From 33595a2c9e4f5a5b81e5028954d7f2df6ef9ee3e Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Mon, 28 Aug 2023 00:33:37 +0200 Subject: [PATCH 01/91] initial openbsd bringup first light add temp sensor code some fixes + some logging add battery code fix temp remove some debugging fix core CPU fix keypress remove debug logging fix Makefile logging mem + swap battery status process args detail process state remove logging disk io minor cleanup --- Makefile | 8 + src/btop.cpp | 8 +- src/btop_tools.cpp | 4 +- src/openbsd/btop_collect.cpp | 1296 ++++++++++++++++++++++++++++++++++ src/openbsd/internal.h | 157 ++++ src/openbsd/sysctlbyname.cpp | 46 ++ src/openbsd/sysctlbyname.h | 20 + 7 files changed, 1534 insertions(+), 5 deletions(-) create mode 100644 src/openbsd/btop_collect.cpp create mode 100644 src/openbsd/internal.h create mode 100644 src/openbsd/sysctlbyname.cpp create mode 100644 src/openbsd/sysctlbyname.h diff --git a/Makefile b/Makefile index 01e98d5..1ff479e 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,8 @@ ifeq ($(CLANG_WORKS),false) CXX := g++11 else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0) CXX := g++ + else ifeq ($(shell command -v eg++ >/dev/null; echo $$?),0) + CXX := eg++ else GCC_NOT_FOUND := true endif @@ -139,6 +141,12 @@ else ifeq ($(PLATFORM_LC),macos) THREADS := $(shell sysctl -n hw.ncpu || echo 1) override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation SU_GROUP := wheel +else ifeq ($(PLATFORM_LC),openbsd) + PLATFORM_DIR := openbsd + THREADS := $(shell sysctl -n hw.ncpu || echo 1) + override ADDFLAGS += -lkvm + export MAKE = gmake + SU_GROUP := wheel else $(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) endif diff --git a/src/btop.cpp b/src/btop.cpp index e53ec02..9a07486 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -243,7 +243,7 @@ void clean_quit(int sig) { Global::quitting = true; Runner::stop(); if (Global::_runner_started) { - #ifdef __APPLE__ + #if defined __APPLE__ || defined __OpenBSD__ if (pthread_join(Runner::runner_id, nullptr) != 0) { Logger::warning("Failed to join _runner thread on exit!"); pthread_cancel(Runner::runner_id); @@ -274,7 +274,7 @@ void clean_quit(int sig) { const auto excode = (sig != -1 ? sig : 0); -#ifdef __APPLE__ +#if defined __APPLE__ || defined __OpenBSD__ _Exit(excode); #else quick_exit(excode); @@ -854,7 +854,7 @@ int main(int argc, char **argv) { catch (...) { found.clear(); } } } - + // #ifdef __APPLE__ if (found.empty()) { CFLocaleRef cflocale = CFLocaleCopyCurrent(); @@ -898,7 +898,7 @@ int main(int argc, char **argv) { Config::set("tty_mode", true); Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols"); } -#ifndef __APPLE__ +#if not defined __APPLE__ && not defined __OpenBSD__ else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) { Config::set("tty_mode", true); Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols"); diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index e642ac6..a8add08 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -123,9 +123,11 @@ namespace Term { tcgetattr(STDIN_FILENO, &initial_settings); current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast(ttyname(STDIN_FILENO)) : "unknown"); - //? Disable stream sync + //? Disable stream sync - this does not seem to work on OpenBSD +#ifndef __OpenBSD__ cin.sync_with_stdio(false); cout.sync_with_stdio(false); +#endif //? Disable stream ties cin.tie(nullptr); diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp new file mode 100644 index 0000000..1d4e774 --- /dev/null +++ b/src/openbsd/btop_collect.cpp @@ -0,0 +1,1296 @@ +/* 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 +#include +#include +#include +#include +// man 3 getifaddrs: "BUGS: If both and are being included, must be included before " +#include +#include +#include +#include +#include +#include +#include // for inet_ntop stuff +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../btop_config.hpp" +#include "../btop_shared.hpp" +#include "../btop_tools.hpp" + +#include "./sysctlbyname.h" + +using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; +using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; +namespace fs = std::filesystem; +namespace rng = std::ranges; +using namespace Tools; + +//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- + +namespace Cpu { + vector core_old_totals; + vector core_old_idles; + vector available_fields = {"total"}; + vector available_sensors = {"Auto"}; + cpu_info current_cpu; + bool got_sensors = false, cpu_temp_only = false; + + //* Populate found_sensors map + bool get_sensors(); + + //* Get current cpu clock speed + string get_cpuHz(); + + //* Search /proc/cpuinfo for a cpu name + string get_cpuName(); + + struct Sensor { + fs::path path; + string label; + int64_t temp = 0; + int64_t high = 0; + int64_t crit = 0; + }; + + string cpu_sensor; + vector core_sensors; + unordered_flat_map core_mapping; +} // namespace Cpu + +namespace Mem { + double old_uptime; +} + +namespace Shared { + + fs::path passwd_path; + uint64_t totalMem; + long pageSize, clkTck, coreCount, physicalCoreCount, arg_max; + int totalMem_len, kfscale; + long bootTime; + + void init() { + //? Shared global variables init + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + int ncpu; + size_t len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, nullptr, 0) == -1) { + Logger::warning("Could not determine number of cores, defaulting to 1."); + } else { + coreCount = ncpu; + } + + pageSize = sysconf(_SC_PAGE_SIZE); + if (pageSize <= 0) { + pageSize = 4096; + Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect."); + } + + clkTck = sysconf(_SC_CLK_TCK); + if (clkTck <= 0) { + clkTck = 100; + Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect."); + } + + int64_t memsize = 0; + size_t size = sizeof(memsize); + if (sysctlbyname("hw.physmem", &memsize, &size, nullptr, 0) < 0) { + Logger::warning("Could not get memory size"); + } + totalMem = memsize; + + struct timeval result; + size = sizeof(result); + if (sysctlbyname("kern.boottime", &result, &size, nullptr, 0) < 0) { + Logger::warning("Could not get boot time"); + } else { + bootTime = result.tv_sec; + } + + size = sizeof(kfscale); + if (sysctlbyname("kern.fscale", &kfscale, &size, nullptr, 0) == -1) { + kfscale = 2048; + } + + //* Get maximum length of process arguments + arg_max = sysconf(_SC_ARG_MAX); + + //? Init for namespace Cpu + Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); + Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); + Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); + Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Cpu::collect(); + for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { + if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); + } + Cpu::cpuName = Cpu::get_cpuName(); + Cpu::got_sensors = Cpu::get_sensors(); + Cpu::core_mapping = Cpu::get_core_mapping(); + + //? Init for namespace Mem + Mem::old_uptime = system_uptime(); + Mem::collect(); + } + + //* RAII wrapper for kvm_openfiles + class kvm_openfiles_wrapper { + kvm_t* kd = nullptr; + public: + kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) { + this->kd = kvm_openfiles(execf, coref, swapf, flags, err); + } + ~kvm_openfiles_wrapper() { kvm_close(kd); } + auto operator()() -> kvm_t* { return kd; } + }; + +} // namespace Shared + +namespace Cpu { + string cpuName; + string cpuHz; + bool has_battery = true; + tuple current_bat; + + const array time_names = {"user", "nice", "system", "idle"}; + + unordered_flat_map cpu_old = { + {"totals", 0}, + {"idles", 0}, + {"user", 0}, + {"nice", 0}, + {"system", 0}, + {"idle", 0} + }; + + string get_cpuName() { + string name; + char buffer[1024]; + size_t size = sizeof(buffer); + if (sysctlbyname("hw.model", &buffer, &size, nullptr, 0) < 0) { + Logger::error("Failed to get CPU name"); + return name; + } + name = string(buffer); + + auto name_vec = ssplit(name); + + if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')')) + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } else if (v_contains(name_vec, "Ryzen"s)) { + auto ryz_pos = v_index(name_vec, "Ryzen"s); + name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "") + (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : ""); + } else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@") + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } else + name.clear(); + + if (name.empty() and not name_vec.empty()) { + for (const auto &n : name_vec) { + if (n == "@") break; + name += n + ' '; + } + name.pop_back(); + for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) { + name = s_replace(name, replace, ""); + name = s_replace(name, " ", " "); + } + name = trim(name); + } + + return name; + } + + int64_t get_sensor(string device, sensor_type type, int num) { + int64_t temp = -1; + struct sensordev sensordev; + struct sensor sensor; + size_t sdlen, slen; + int dev; + int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0}; + + sdlen = sizeof(sensordev); + slen = sizeof(sensor); + for (dev = 0;; dev++) { + mib[2] = dev; + if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { + if (errno == ENXIO) + continue; + if (errno == ENOENT) + break; + } + if (strstr(sensordev.xname, device.c_str())) { + mib[3] = type; + mib[4] = num; + if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { + if (errno != ENOENT) { + Logger::warning("sysctl"); + continue; + } + } + temp = sensor.value; + break; + } + } + return temp; + } + + bool get_sensors() { + got_sensors = false; + if (Config::getB("show_coretemp") and Config::getB("check_temp")) { + if (get_sensor(string("cpu0") , SENSOR_TEMP, 0) > 0) { + got_sensors = true; + current_cpu.temp_max = 100; // we don't have this info + } else { + Logger::warning("Could not get temp sensor"); + } + } + return got_sensors; + } + +#define MUKTOC(v) ((v - 273150000) / 1000000.0) + + void update_sensors() { + int temp = 0; + int p_temp = 0; + + temp = get_sensor(string("cpu0"), SENSOR_TEMP, 0); + if (temp > -1) { + temp = MUKTOC(temp); + p_temp = temp; + for (int i = 0; i < Shared::coreCount; i++) { + if (cmp_less(i + 1, current_cpu.temp.size())) { + current_cpu.temp.at(i + 1).push_back(temp); + if (current_cpu.temp.at(i + 1).size() > 20) + current_cpu.temp.at(i + 1).pop_front(); + } + } + current_cpu.temp.at(0).push_back(p_temp); + if (current_cpu.temp.at(0).size() > 20) + current_cpu.temp.at(0).pop_front(); + } + + } + + string get_cpuHz() { + unsigned int freq = 1; + size_t size = sizeof(freq); + + if (sysctlbyname("hw.cpuspeed", &freq, &size, nullptr, 0) < 0) { + return ""; + } + return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz + } + + auto get_core_mapping() -> unordered_flat_map { + unordered_flat_map core_map; + if (cpu_temp_only) return core_map; + + for (long i = 0; i < Shared::coreCount; i++) { + core_map[i] = i; + } + + //? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc. + if (cmp_less(core_map.size(), Shared::coreCount)) { + if (Shared::coreCount % 2 == 0 and (long) core_map.size() == Shared::coreCount / 2) { + for (int i = 0, n = 0; i < Shared::coreCount / 2; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[Shared::coreCount / 2 + i] = n++; + } + } else { + core_map.clear(); + for (int i = 0, n = 0; i < Shared::coreCount; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[i] = n++; + } + } + } + + //? Apply user set custom mapping if any + const auto &custom_map = Config::getS("cpu_core_map"); + if (not custom_map.empty()) { + try { + for (const auto &split : ssplit(custom_map)) { + const auto vals = ssplit(split, ':'); + if (vals.size() != 2) continue; + int change_id = std::stoi(vals.at(0)); + int new_id = std::stoi(vals.at(1)); + if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue; + core_map.at(change_id) = new_id; + } + } catch (...) { + } + } + + return core_map; + } + + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, ""}; + + long seconds = -1; + uint32_t percent = -1; + string status = "discharging"; + int64_t full, remaining; + full = get_sensor("acpibat0", SENSOR_AMPHOUR, 0); + remaining = get_sensor("acpibat0", SENSOR_AMPHOUR, 3); + int64_t state = get_sensor("acpibat0", SENSOR_INTEGER, 0); + if (full < 0) { + has_battery = false; + Logger::warning("failed to get battery"); + } else { + float_t f = full / 1000; + float_t r = remaining / 1000; + has_battery = true; + percent = r / f * 100; + if (percent == 100) { + status = "full"; + } + switch (state) { + case 0: + status = "full"; + percent = 100; + break; + case 2: + status = "charging"; + break; + } + } + + return {percent, seconds, status}; + } + + auto collect(bool no_update) -> cpu_info & { + if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) + return current_cpu; + auto &cpu = current_cpu; + + if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) { + Logger::error("failed to get load averages"); + } + + auto cp_time = std::unique_ptr{ + new struct cpustats[Shared::coreCount] + }; + size_t size = Shared::coreCount * sizeof(struct cpustats); + static int cpustats_mib[] = {CTL_KERN, KERN_CPUSTATS, /*fillme*/0}; + for (int i = 0; i < Shared::coreCount; i++) { + cpustats_mib[2] = i / 2; + if (sysctl(cpustats_mib, 3, &cp_time[i], &size, NULL, 0) == -1) { + Logger::error("sysctl kern.cpustats failed"); + } + } + long long global_totals = 0; + long long global_idles = 0; + vector times_summed = {0, 0, 0, 0}; + + for (long i = 0; i < Shared::coreCount; i++) { + vector times; + //? 0=user, 1=nice, 2=system, 3=idle + for (int x = 0; const unsigned int c_state : {CP_USER, CP_NICE, CP_SYS, CP_IDLE}) { + auto val = cp_time[i].cs_time[c_state]; + times.push_back(val); + times_summed.at(x++) += val; + } + try { + //? All values + const long long totals = std::accumulate(times.begin(), times.end(), 0ll); + + //? Idle time + const long long idles = times.at(3); + + global_totals += totals; + global_idles += idles; + + //? Calculate cpu total for each core + if (i > Shared::coreCount) break; + const long long calc_totals = max(0ll, totals - core_old_totals.at(i)); + const long long calc_idles = max(0ll, idles - core_old_idles.at(i)); + core_old_totals.at(i) = totals; + core_old_idles.at(i) = idles; + + cpu.core_percent.at(i).push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); + + //? Reduce size if there are more values than needed for graph + if (cpu.core_percent.at(i).size() > 40) cpu.core_percent.at(i).pop_front(); + + } catch (const std::exception &e) { + Logger::error("Cpu::collect() : " + (string)e.what()); + throw std::runtime_error("collect() : " + (string)e.what()); + } + + } + + const long long calc_totals = max(1ll, global_totals - cpu_old.at("totals")); + const long long calc_idles = max(1ll, global_idles - cpu_old.at("idles")); + + //? Populate cpu.cpu_percent with all fields from syscall + for (int ii = 0; const auto &val : times_summed) { + cpu.cpu_percent.at(time_names.at(ii)).push_back(clamp((long long)round((double)(val - cpu_old.at(time_names.at(ii))) * 100 / calc_totals), 0ll, 100ll)); + cpu_old.at(time_names.at(ii)) = val; + + //? Reduce size if there are more values than needed for graph + while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front(); + + ii++; + } + + cpu_old.at("totals") = global_totals; + cpu_old.at("idles") = global_idles; + + //? Total usage of cpu + cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll)); + + //? Reduce size if there are more values than needed for graph + while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front(); + + if (Config::getB("show_cpu_freq")) { + auto hz = get_cpuHz(); + if (hz != "") { + cpuHz = hz; + } + } + + if (Config::getB("check_temp") and got_sensors) + update_sensors(); + + if (Config::getB("show_battery") and has_battery) + current_bat = get_battery(); + + return cpu; + } +} // namespace Cpu + +namespace Mem { + bool has_swap = false; + vector fstab; + fs::file_time_type fstab_time; + int disk_ios = 0; + vector last_found; + + mem_info current_mem{}; + + uint64_t get_totalMem() { + return Shared::totalMem; + } + + void assign_values(struct disk_info& disk, int64_t readBytes, int64_t writeBytes) { + disk_ios++; + if (disk.io_read.empty()) { + disk.io_read.push_back(0); + } else { + disk.io_read.push_back(max((int64_t)0, (readBytes - disk.old_io.at(0)))); + } + disk.old_io.at(0) = readBytes; + while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + + if (disk.io_write.empty()) { + disk.io_write.push_back(0); + } else { + disk.io_write.push_back(max((int64_t)0, (writeBytes - disk.old_io.at(1)))); + } + disk.old_io.at(1) = writeBytes; + while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); + + // no io times - need to push something anyway or we'll get an ABORT + if (disk.io_activity.empty()) + disk.io_activity.push_back(0); + else + disk.io_activity.push_back(clamp((long)round((double)(disk.io_write.back() + disk.io_read.back()) / (1 << 20)), 0l, 100l)); + while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); + } + + void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + uint64_t total_bytes_read = 0; + uint64_t total_bytes_write = 0; + + int num_drives = 0; + int mib[2] = { CTL_HW, HW_DISKCOUNT }; + + size_t size; + if (sysctl(mib, 2, &num_drives, &size, NULL, 0) >= 0) { + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + size = num_drives * sizeof(struct diskstats); + auto p = std::unique_ptr { + reinterpret_cast(malloc(size)), + free + }; + if (sysctl(mib, 2, p.get(), &size, NULL, 0) == -1) { + Logger::error("failed to get disk stats"); + return; + } + for (int i = 0; i < num_drives; i++) { + for (auto& [ignored, disk] : disks) { + if (disk.dev.string().find(p[i].ds_name) != string::npos) { + string mountpoint = mapping.at(disk.dev); + total_bytes_read = p[i].ds_rbytes; + total_bytes_write = p[i].ds_wbytes; + assign_values(disk, total_bytes_read, total_bytes_write); + } + } + } + } + } + + auto collect(bool no_update) -> mem_info & { + if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) + return current_mem; + + auto show_swap = Config::getB("show_swap"); + auto show_disks = Config::getB("show_disks"); + auto swap_disk = Config::getB("swap_disk"); + auto &mem = current_mem; + static bool snapped = (getenv("BTOP_SNAPPED") != nullptr); + + u_int memActive, memWire, cachedMem; + // u_int freeMem; + size_t size; + static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + static int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; + struct uvmexp uvmexp; + struct bcachestats bcstats; + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&uvmexp, sizeof(uvmexp)); + } + size = sizeof(bcstats); + if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) { + Logger::error("sysctl failed"); + bzero(&bcstats, sizeof(bcstats)); + } + memActive = uvmexp.active * Shared::pageSize; + memWire = uvmexp.wired; + // freeMem = uvmexp.free * Shared::pageSize; + cachedMem = bcstats.numbufpages * Shared::pageSize; + mem.stats.at("used") = memActive; + mem.stats.at("available") = Shared::totalMem - memActive - memWire; + mem.stats.at("cached") = cachedMem; + mem.stats.at("free") = Shared::totalMem - memActive - memWire; + + if (show_swap) { + int total = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_total") = total; + int swapped = uvmexp.swpgonly * Shared::pageSize; + mem.stats.at("swap_used") = swapped; + mem.stats.at("swap_free") = total - swapped; + } + + if (show_swap and mem.stats.at("swap_total") > 0) { + for (const auto &name : swap_names) { + mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / mem.stats.at("swap_total"))); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); + } + has_swap = true; + } else + has_swap = false; + //? Calculate percentages + for (const auto &name : mem_names) { + mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / Shared::totalMem)); + while (cmp_greater(mem.percent.at(name).size(), width * 2)) + mem.percent.at(name).pop_front(); + } + + if (show_disks) { + unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + double uptime = system_uptime(); + auto &disks_filter = Config::getS("disks_filter"); + bool filter_exclude = false; + // auto only_physical = Config::getB("only_physical"); + auto &disks = mem.disks; + vector filter; + if (not disks_filter.empty()) { + filter = ssplit(disks_filter); + if (filter.at(0).starts_with("exclude=")) { + filter_exclude = true; + filter.at(0) = filter.at(0).substr(8); + } + } + + struct statfs *stfs; + int count = getmntinfo(&stfs, MNT_WAIT); + vector found; + found.reserve(last_found.size()); + for (int i = 0; i < count; i++) { + auto fstype = string(stfs[i].f_fstypename); + if (fstype == "autofs" || fstype == "devfs" || fstype == "linprocfs" || fstype == "procfs" || fstype == "tmpfs" || fstype == "linsysfs" || + fstype == "fdesckfs") { + // in memory filesystems -> not useful to show + continue; + } + + std::error_code ec; + string mountpoint = stfs[i].f_mntonname; + string dev = stfs[i].f_mntfromname; + mapping[dev] = mountpoint; + + //? Match filter if not empty + if (not filter.empty()) { + bool match = v_contains(filter, mountpoint); + if ((filter_exclude and match) or (not filter_exclude and not match)) + continue; + } + + found.push_back(mountpoint); + if (not disks.contains(mountpoint)) { + disks[mountpoint] = disk_info{fs::canonical(dev, ec), fs::path(mountpoint).filename()}; + + if (disks.at(mountpoint).dev.empty()) + disks.at(mountpoint).dev = dev; + + if (disks.at(mountpoint).name.empty()) + disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint); + } + + + if (not v_contains(last_found, mountpoint)) + redraw = true; + + disks.at(mountpoint).free = stfs[i].f_bfree; + disks.at(mountpoint).total = stfs[i].f_iosize; + } + + //? Remove disks no longer mounted or filtered out + if (swap_disk and has_swap) found.push_back("swap"); + for (auto it = disks.begin(); it != disks.end();) { + if (not v_contains(found, it->first)) + it = disks.erase(it); + else + it++; + } + if (found.size() != last_found.size()) redraw = true; + last_found = std::move(found); + + //? Get disk/partition stats + for (auto &[mountpoint, disk] : disks) { + if (std::error_code ec; not fs::exists(mountpoint, ec)) + continue; + struct statvfs vfs; + if (statvfs(mountpoint.c_str(), &vfs) < 0) { + Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint); + continue; + } + disk.total = vfs.f_blocks * vfs.f_frsize; + disk.free = vfs.f_bfree * vfs.f_frsize; + disk.used = disk.total - disk.free; + disk.used_percent = round((double)disk.used * 100 / disk.total); + disk.free_percent = 100 - disk.used_percent; + } + + //? Setup disks order in UI and add swap if enabled + mem.disks_order.clear(); + if (snapped and disks.contains("/mnt")) + mem.disks_order.push_back("/mnt"); + else if (disks.contains("/")) + mem.disks_order.push_back("/"); + if (swap_disk and has_swap) { + mem.disks_order.push_back("swap"); + if (not disks.contains("swap")) + disks["swap"] = {"", "swap"}; + disks.at("swap").total = mem.stats.at("swap_total"); + disks.at("swap").used = mem.stats.at("swap_used"); + disks.at("swap").free = mem.stats.at("swap_free"); + disks.at("swap").used_percent = mem.percent.at("swap_used").back(); + disks.at("swap").free_percent = mem.percent.at("swap_free").back(); + } + for (const auto &name : last_found) + if (not is_in(name, "/", "swap", "/dev")) + mem.disks_order.push_back(name); + + disk_ios = 0; + collect_disk(disks, mapping); + + old_uptime = uptime; + } + return mem; + } + +} // namespace Mem + +namespace Net { + unordered_flat_map current_net; + net_info empty_net = {}; + vector interfaces; + string selected_iface; + int errors = 0; + unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; + unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + bool rescale = true; + uint64_t timestamp = 0; + + //* RAII wrapper for getifaddrs + class getifaddr_wrapper { + struct ifaddrs *ifaddr; + + public: + int status; + getifaddr_wrapper() { status = getifaddrs(&ifaddr); } + ~getifaddr_wrapper() { freeifaddrs(ifaddr); } + auto operator()() -> struct ifaddrs * { return ifaddr; } + }; + + auto collect(bool no_update) -> net_info & { + auto &net = current_net; + auto &config_iface = Config::getS("net_iface"); + auto net_sync = Config::getB("net_sync"); + auto net_auto = Config::getB("net_auto"); + auto new_timestamp = time_ms(); + + if (not no_update and errors < 3) { + //? Get interface list using getifaddrs() wrapper + getifaddr_wrapper if_wrap{}; + if (if_wrap.status != 0) { + errors++; + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); + redraw = true; + return empty_net; + } + int family = 0; + static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); // 46 >= 16, compile-time assurance. + enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN }; // manually using the known biggest value, guarded by the above static_assert + char ip[IPBUFFER_MAXSIZE]; + interfaces.clear(); + string ipv4, ipv6; + + //? Iteration over all items in getifaddrs() list + for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) continue; + family = ifa->ifa_addr->sa_family; + const auto &iface = ifa->ifa_name; + //? Update available interfaces vector and get status of interface + if (not v_contains(interfaces, iface)) { + interfaces.push_back(iface); + net[iface].connected = (ifa->ifa_flags & IFF_RUNNING); + + // An interface can have more than one IP of the same family associated with it, + // but we pick only the first one to show in the NET box. + // Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable! + net[iface].ipv4.clear(); + net[iface].ipv6.clear(); + } + //? Get IPv4 address + if (family == AF_INET) { + if (net[iface].ipv4.empty()) { + if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin_addr), ip, IPBUFFER_MAXSIZE)) { + + net[iface].ipv4 = ip; + } else { + int errsv = errno; + Logger::error("Net::collect() -> Failed to convert IPv4 to string for iface " + string(iface) + ", errno: " + strerror(errsv)); + } + } + } + //? Get IPv6 address + else if (family == AF_INET6) { + if (net[iface].ipv6.empty()) { + if (nullptr != inet_ntop(family, &(reinterpret_cast(ifa->ifa_addr)->sin6_addr), ip, IPBUFFER_MAXSIZE)) { + net[iface].ipv6 = ip; + } else { + int errsv = errno; + Logger::error("Net::collect() -> Failed to convert IPv6 to string for iface " + string(iface) + ", errno: " + strerror(errsv)); + } + } + } //else, ignoring family==AF_LINK (see man 3 getifaddrs) + } + + unordered_flat_map> ifstats; + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; + size_t len; + if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { + Logger::error("failed getting network interfaces"); + } else { + std::unique_ptr buf(new char[len]); + if (sysctl(mib, 6, buf.get(), &len, nullptr, 0) < 0) { + Logger::error("failed getting network interfaces"); + } else { + char *lim = buf.get() + len; + char *next = nullptr; + for (next = buf.get(); next < lim;) { + struct if_msghdr *ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + struct if_data ifm_data = ifm->ifm_data; + if (ifm->ifm_addrs & RTA_IFP) { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(ifm + 1); + char iface[32]; + strncpy(iface, sdl->sdl_data, sdl->sdl_nlen); + iface[sdl->sdl_nlen] = 0; + ifstats[iface] = std::tuple(ifm_data.ifi_ibytes, ifm_data.ifi_obytes); + } + } + } + } + + //? Get total recieved and transmitted bytes + device address if no ip was found + for (const auto &iface : interfaces) { + for (const string dir : {"download", "upload"}) { + auto &saved_stat = net.at(iface).stat.at(dir); + auto &bandwidth = net.at(iface).bandwidth.at(dir); + uint64_t val = dir == "download" ? std::get<0>(ifstats[iface]) : std::get<1>(ifstats[iface]); + + //? Update speed, total and top values + if (val < saved_stat.last) { + saved_stat.rollover += saved_stat.last; + saved_stat.last = 0; + } + if (cmp_greater((unsigned long long)saved_stat.rollover + (unsigned long long)val, numeric_limits::max())) { + saved_stat.rollover = 0; + saved_stat.last = 0; + } + saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000)); + if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed; + if (saved_stat.offset > val + saved_stat.rollover) saved_stat.offset = 0; + saved_stat.total = (val + saved_stat.rollover) - saved_stat.offset; + saved_stat.last = val; + + //? Add values to graph + bandwidth.push_back(saved_stat.speed); + while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front(); + + //? Set counters for auto scaling + if (net_auto and selected_iface == iface) { + if (saved_stat.speed > graph_max[dir]) { + ++max_count[dir][0]; + if (max_count[dir][1] > 0) --max_count[dir][1]; + } else if (graph_max[dir] > 10 << 10 and saved_stat.speed < graph_max[dir] / 10) { + ++max_count[dir][1]; + if (max_count[dir][0] > 0) --max_count[dir][0]; + } + } + } + } + + //? Clean up net map if needed + if (net.size() > interfaces.size()) { + for (auto it = net.begin(); it != net.end();) { + if (not v_contains(interfaces, it->first)) + it = net.erase(it); + else + it++; + } + net.compact(); + } + + timestamp = new_timestamp; + } + //? Return empty net_info struct if no interfaces was found + if (net.empty()) + return empty_net; + + //? Find an interface to display if selected isn't set or valid + if (selected_iface.empty() or not v_contains(interfaces, selected_iface)) { + max_count["download"][0] = max_count["download"][1] = max_count["upload"][0] = max_count["upload"][1] = 0; + redraw = true; + if (net_auto) rescale = true; + if (not config_iface.empty() and v_contains(interfaces, config_iface)) + selected_iface = config_iface; + else { + //? Sort interfaces by total upload + download bytes + auto sorted_interfaces = interfaces; + rng::sort(sorted_interfaces, [&](const auto &a, const auto &b) { + return cmp_greater(net.at(a).stat["download"].total + net.at(a).stat["upload"].total, + net.at(b).stat["download"].total + net.at(b).stat["upload"].total); + }); + selected_iface.clear(); + //? Try to set to a connected interface + for (const auto &iface : sorted_interfaces) { + if (net.at(iface).connected) selected_iface = iface; + break; + } + //? If no interface is connected set to first available + if (selected_iface.empty() and not sorted_interfaces.empty()) + selected_iface = sorted_interfaces.at(0); + else if (sorted_interfaces.empty()) + return empty_net; + } + } + + //? Calculate max scale for graphs if needed + if (net_auto) { + bool sync = false; + for (const auto &dir : {"download", "upload"}) { + for (const auto &sel : {0, 1}) { + if (rescale or max_count[dir][sel] >= 5) { + const long long avg_speed = (net[selected_iface].bandwidth[dir].size() > 5 + ? std::accumulate(net.at(selected_iface).bandwidth.at(dir).rbegin(), net.at(selected_iface).bandwidth.at(dir).rbegin() + 5, 0ll) / 5 + : net[selected_iface].stat[dir].speed); + graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10); + max_count[dir][0] = max_count[dir][1] = 0; + redraw = true; + if (net_sync) sync = true; + break; + } + } + //? Sync download/upload graphs if enabled + if (sync) { + const auto other = (string(dir) == "upload" ? "download" : "upload"); + graph_max[other] = graph_max[dir]; + max_count[other][0] = max_count[other][1] = 0; + break; + } + } + } + + rescale = false; + return net.at(selected_iface); + } +} // namespace Net + +namespace Proc { + + vector current_procs; + unordered_flat_map uid_user; + string current_sort; + string current_filter; + bool current_rev = false; + + fs::file_time_type passwd_time; + + uint64_t cputimes; + int collapse = -1, expand = -1; + uint64_t old_cputimes = 0; + atomic numpids = 0; + int filter_found = 0; + + detail_container detailed; + + string get_status(char s) { + if (s & SRUN) return "Running"; + if (s & SSLEEP) return "Sleeping"; + if (s & SIDL) return "Idle"; + if (s & SSTOP) return "Stopped"; + if (s & SZOMB) return "Zombie"; + return "Unknown"; + } + + //* Get detailed info for selected process + void _collect_details(const size_t pid, vector &procs) { + if (pid != detailed.last_pid) { + detailed = {}; + detailed.last_pid = pid; + detailed.skip_smaps = not Config::getB("proc_info_smaps"); + } + + //? Copy proc_info for process from proc vector + auto p_info = rng::find(procs, pid, &proc_info::pid); + detailed.entry = *p_info; + + //? Update cpu percent deque for process cpu graph + if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount; + detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll)); + while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front(); + + //? Process runtime : current time - start time (both in unix time - seconds since epoch) + struct timeval currentTime; + gettimeofday(¤tTime, nullptr); + detailed.elapsed = sec_to_dhms(currentTime.tv_sec - detailed.entry.cpu_s); // only interested in second granularity, so ignoring tc_usec + if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3); + + //? Get parent process name + if (detailed.parent.empty()) { + auto p_entry = rng::find(procs, detailed.entry.ppid, &proc_info::pid); + if (p_entry != procs.end()) detailed.parent = p_entry->name; + } + + //? Expand process status from single char to explanative string + detailed.status = get_status(detailed.entry.state); + + detailed.mem_bytes.push_back(detailed.entry.mem); + detailed.memory = floating_humanizer(detailed.entry.mem); + + if (detailed.first_mem == -1 or detailed.first_mem < detailed.mem_bytes.back() / 2 or detailed.first_mem > detailed.mem_bytes.back() * 4) { + detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Mem::get_totalMem()); + redraw = true; + } + + while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front(); + } + + //* Collects and sorts process information from /proc + auto collect(bool no_update) -> vector & { + const auto &sorting = Config::getS("proc_sorting"); + auto reverse = Config::getB("proc_reversed"); + const auto &filter = Config::getS("proc_filter"); + auto per_core = Config::getB("proc_per_core"); + auto tree = Config::getB("proc_tree"); + auto show_detailed = Config::getB("show_detailed"); + const size_t detailed_pid = Config::getI("detailed_pid"); + bool should_filter = current_filter != filter; + if (should_filter) current_filter = filter; + bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter); + if (sorted_change) { + current_sort = sorting; + current_rev = reverse; + } + + const int cmult = (per_core) ? Shared::coreCount : 1; + bool got_detailed = false; + + static vector found; + + //* Use pids from last update if only changing filter, sorting or tree options + if (no_update and not current_procs.empty()) { + if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, current_procs); + } else { + //* ---------------------------------------------Collection start---------------------------------------------- + + should_filter = true; + found.clear(); + struct timeval currentTime; + gettimeofday(¤tTime, nullptr); + const double timeNow = currentTime.tv_sec + (currentTime.tv_usec / 1'000'000); + + int count = 0; + char buf[_POSIX2_LINE_MAX]; + Shared::kvm_openfiles_wrapper kd(nullptr, nullptr, nullptr, KVM_NO_FILES, buf); + const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count); + + for (int i = 0; i < count; i++) { + const struct kinfo_proc* kproc = &kprocs[i]; + const size_t pid = (size_t)kproc->p_pid; + if (pid < 1) continue; + found.push_back(pid); + + //? Check if pid already exists in current_procs + bool no_cache = false; + auto find_old = rng::find(current_procs, pid, &proc_info::pid); + if (find_old == current_procs.end()) { + current_procs.push_back({pid}); + find_old = current_procs.end() - 1; + no_cache = true; + } + + auto &new_proc = *find_old; + + //? Get program name, command, username, parent pid, nice and status + if (no_cache) { + if (string(kproc->p_comm) == "idle"s) { + current_procs.pop_back(); + found.pop_back(); + continue; + } + new_proc.name = kproc->p_comm; + char** argv = kvm_getargv(kd(), kproc, 0); + if (argv) { + for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { + new_proc.cmd += argv[i] + " "s; + } + if (not new_proc.cmd.empty()) new_proc.cmd.pop_back(); + } + if (new_proc.cmd.empty()) new_proc.cmd = new_proc.name; + if (new_proc.cmd.size() > 1000) { + new_proc.cmd.resize(1000); + new_proc.cmd.shrink_to_fit(); + } + new_proc.ppid = kproc->p_ppid; + new_proc.cpu_s = round(kproc->p_ustart_sec); + struct passwd *pwd = getpwuid(kproc->p_uid); + if (pwd) + new_proc.user = pwd->pw_name; + } + new_proc.p_nice = kproc->p_nice; + new_proc.state = kproc->p_stat; + + int cpu_t = 0; + cpu_t = kproc->p_uctime_usec * 1'000'000 + kproc->p_uctime_sec; + + new_proc.mem = kproc->p_vm_rssize * Shared::pageSize; + new_proc.threads = 1; // can't seem to find this in kinfo_proc + + //? Process cpu usage since last update + new_proc.cpu_p = clamp((100.0 * kproc->p_pctcpu / Shared::kfscale) * cmult, 0.0, 100.0 * Shared::coreCount); + + //? Process cumulative cpu usage since process start + new_proc.cpu_c = (double)(cpu_t * Shared::clkTck / 1'000'000) / max(1.0, timeNow - new_proc.cpu_s); + + //? Update cached value with latest cpu times + new_proc.cpu_t = cpu_t; + + if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) { + got_detailed = true; + } + } + + //? Clear dead processes from current_procs + auto eraser = rng::remove_if(current_procs, [&](const auto &element) { return not v_contains(found, element.pid); }); + current_procs.erase(eraser.begin(), eraser.end()); + + //? Update the details info box for process if active + if (show_detailed and got_detailed) { + _collect_details(detailed_pid, current_procs); + } else if (show_detailed and not got_detailed and detailed.status != "Dead") { + detailed.status = "Dead"; + redraw = true; + } + + old_cputimes = cputimes; + + } + + //* ---------------------------------------------Collection done----------------------------------------------- + + //* Match filter if defined + if (should_filter) { + filter_found = 0; + for (auto& p : current_procs) { + if (not tree and not filter.empty()) { + if (not s_contains_ic(to_string(p.pid), filter) + and not s_contains_ic(p.name, filter) + and not s_contains_ic(p.cmd, filter) + and not s_contains_ic(p.user, filter)) { + p.filtered = true; + filter_found++; + } + else { + p.filtered = false; + } + } + else { + p.filtered = false; + } + } + } + + //* Sort processes + if (sorted_change or not no_update) { + proc_sorter(current_procs, sorting, reverse, tree); + } + + //* Generate tree view if enabled + if (tree and (not no_update or should_filter or sorted_change)) { + bool locate_selection = false; + if (auto find_pid = (collapse != -1 ? collapse : expand); find_pid != -1) { + auto collapser = rng::find(current_procs, find_pid, &proc_info::pid); + if (collapser != current_procs.end()) { + if (collapse == expand) { + collapser->collapsed = not collapser->collapsed; + } + else if (collapse > -1) { + collapser->collapsed = true; + } + else if (expand > -1) { + collapser->collapsed = false; + } + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + collapse = expand = -1; + } + if (should_filter or not filter.empty()) filter_found = 0; + + vector tree_procs; + tree_procs.reserve(current_procs.size()); + + for (auto& p : current_procs) { + if (not v_contains(found, p.ppid)) p.ppid = 0; + } + + //? Stable sort to retain selected sorting among processes with the same parent + rng::stable_sort(current_procs, rng::less{}, & proc_info::ppid); + + //? Start recursive iteration over processes with the lowest shared parent pids + for (auto& p : rng::equal_range(current_procs, current_procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { + _tree_gen(p, current_procs, tree_procs, 0, false, filter, false, no_update, should_filter); + } + + //? Recursive sort over tree structure to account for collapsed processes in the tree + int index = 0; + tree_sort(tree_procs, sorting, reverse, index, current_procs.size()); + + //? Add tree begin symbol to first item if childless + if (tree_procs.front().children.empty()) + tree_procs.front().entry.get().prefix.replace(tree_procs.front().entry.get().prefix.size() - 8, 8, " ┌─ "); + + //? Add tree terminator symbol to last item if childless + if (tree_procs.back().children.empty()) + tree_procs.back().entry.get().prefix.replace(tree_procs.back().entry.get().prefix.size() - 8, 8, " └─ "); + + //? Final sort based on tree index + rng::sort(current_procs, rng::less{}, & proc_info::tree_index); + + //? Move current selection/view to the selected process when collapsing/expanding in the tree + if (locate_selection) { + int loc = rng::find(current_procs, Proc::selected_pid, &proc_info::pid)->tree_index; + if (Config::ints.at("proc_start") >= loc or Config::ints.at("proc_start") <= loc - Proc::select_max) + Config::ints.at("proc_start") = max(0, loc - 1); + Config::ints.at("proc_selected") = loc - Config::ints.at("proc_start") + 1; + } + } + + numpids = (int)current_procs.size() - filter_found; + return current_procs; + } +} // namespace Proc + +namespace Tools { + double system_uptime() { + struct timeval ts, currTime; + std::size_t len = sizeof(ts); + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + if (sysctl(mib, 2, &ts, &len, nullptr, 0) != -1) { + gettimeofday(&currTime, nullptr); + return currTime.tv_sec - ts.tv_sec; + } + return 0.0; + } +} // namespace Tools diff --git a/src/openbsd/internal.h b/src/openbsd/internal.h new file mode 100644 index 0000000..17840b6 --- /dev/null +++ b/src/openbsd/internal.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019-2021 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct sysctls { + const char *name; + int mib0; + int mib1; + int mib2; +} sysctlnames[] = { + { "hw.machine", CTL_HW, HW_MACHINE, 0 }, + { "hw.model", CTL_HW, HW_MODEL, 0 }, + { "hw.ncpu", CTL_HW, HW_NCPU, 0 }, + { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 }, + { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 }, + { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 }, + { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 }, + { "hw.sensors", CTL_HW, HW_SENSORS, 0 }, + { "hw.model", CTL_HW, HW_MODEL, 0 }, + { "hw.ncpu", CTL_HW, HW_NCPU, 0 }, + { "hw.byteorder", CTL_HW, HW_BYTEORDER, 0 }, + { "hw.pagesize", CTL_HW, HW_PAGESIZE, 0 }, + { "hw.disknames", CTL_HW, HW_DISKNAMES, 0 }, + { "hw.diskcount", CTL_HW, HW_DISKCOUNT, 0 }, + { "hw.sensors", CTL_HW, HW_SENSORS, 0 }, + { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 }, + { "hw.setperf", CTL_HW, HW_SETPERF, 0 }, + { "hw.vendor", CTL_HW, HW_VENDOR, 0 }, + { "hw.product", CTL_HW, HW_PRODUCT, 0 }, + { "hw.serialno", CTL_HW, HW_SERIALNO, 0 }, + { "hw.uuid", CTL_HW, HW_UUID, 0 }, + { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 }, + { "hw.usermem", CTL_HW, HW_USERMEM64, 0 }, + { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 }, + { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 }, + { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 }, + { "hw.smt", CTL_HW, HW_SMT, 0 }, + { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 }, + { "hw.cpuspeed", CTL_HW, HW_CPUSPEED, 0 }, + { "hw.setperf", CTL_HW, HW_SETPERF, 0 }, + { "hw.vendor", CTL_HW, HW_VENDOR, 0 }, + { "hw.product", CTL_HW, HW_PRODUCT, 0 }, + { "hw.serialno", CTL_HW, HW_SERIALNO, 0 }, + { "hw.uuid", CTL_HW, HW_UUID, 0 }, + { "hw.physmem", CTL_HW, HW_PHYSMEM64, 0 }, + { "hw.usermem", CTL_HW, HW_USERMEM64, 0 }, + { "hw.ncpufound", CTL_HW, HW_NCPUFOUND, 0 }, + { "hw.allowpowerdown", CTL_HW, HW_ALLOWPOWERDOWN, 0 }, + { "hw.perfpolicy", CTL_HW, HW_PERFPOLICY, 0 }, + { "hw.smt", CTL_HW, HW_SMT, 0 }, + { "hw.ncpuonline", CTL_HW, HW_NCPUONLINE, 0 }, + { "kern.ostype", CTL_KERN, KERN_OSTYPE, 0 }, + { "kern.osrelease", CTL_KERN, KERN_OSRELEASE, 0 }, + { "kern.osrevision", CTL_KERN, KERN_OSREV, 0 }, + { "kern.version", CTL_KERN, KERN_VERSION, 0 }, + { "kern.maxvnodes", CTL_KERN, KERN_MAXVNODES, 0 }, + { "kern.maxproc", CTL_KERN, KERN_MAXPROC, 0 }, + { "kern.maxfiles", CTL_KERN, KERN_MAXFILES, 0 }, + { "kern.argmax", CTL_KERN, KERN_ARGMAX, 0 }, + { "kern.securelevel", CTL_KERN, KERN_SECURELVL, 0 }, + { "kern.hostname", CTL_KERN, KERN_HOSTNAME, 0 }, + { "kern.hostid", CTL_KERN, KERN_HOSTID, 0 }, + { "kern.clockrate", CTL_KERN, KERN_CLOCKRATE, 0 }, + { "kern.profiling", CTL_KERN, KERN_PROF, 0 }, + { "kern.posix1version", CTL_KERN, KERN_POSIX1, 0 }, + { "kern.ngroups", CTL_KERN, KERN_NGROUPS, 0 }, + { "kern.job_control", CTL_KERN, KERN_JOB_CONTROL, 0 }, + { "kern.saved_ids", CTL_KERN, KERN_SAVED_IDS, 0 }, + { "kern.boottime", CTL_KERN, KERN_BOOTTIME, 0 }, + { "kern.domainname", CTL_KERN, KERN_DOMAINNAME, 0 }, + { "kern.maxpartitions", CTL_KERN, KERN_MAXPARTITIONS, 0 }, + { "kern.rawpartition", CTL_KERN, KERN_RAWPARTITION, 0 }, + { "kern.maxthread", CTL_KERN, KERN_MAXTHREAD, 0 }, + { "kern.nthreads", CTL_KERN, KERN_NTHREADS, 0 }, + { "kern.osversion", CTL_KERN, KERN_OSVERSION, 0 }, + { "kern.somaxconn", CTL_KERN, KERN_SOMAXCONN, 0 }, + { "kern.sominconn", CTL_KERN, KERN_SOMINCONN, 0 }, + { "kern.nosuidcoredump", CTL_KERN, KERN_NOSUIDCOREDUMP, 0 }, + { "kern.fsync", CTL_KERN, KERN_FSYNC, 0 }, + { "kern.sysvmsg", CTL_KERN, KERN_SYSVMSG, 0 }, + { "kern.sysvsem", CTL_KERN, KERN_SYSVSEM, 0 }, + { "kern.sysvshm", CTL_KERN, KERN_SYSVSHM, 0 }, + { "kern.msgbufsize", CTL_KERN, KERN_MSGBUFSIZE, 0 }, + { "kern.malloc", CTL_KERN, KERN_MALLOCSTATS, 0 }, + { "kern.cp_time", CTL_KERN, KERN_CPTIME, 0 }, + { "kern.nchstats", CTL_KERN, KERN_NCHSTATS, 0 }, + { "kern.forkstat", CTL_KERN, KERN_FORKSTAT, 0 }, + { "kern.tty", CTL_KERN, KERN_TTY, 0 }, + { "kern.ccpu", CTL_KERN, KERN_CCPU, 0 }, + { "kern.fscale", CTL_KERN, KERN_FSCALE, 0 }, + { "kern.nprocs", CTL_KERN, KERN_NPROCS, 0 }, + { "kern.msgbuf", CTL_KERN, KERN_MSGBUF, 0 }, + { "kern.pool", CTL_KERN, KERN_POOL, 0 }, + { "kern.stackgap_random", CTL_KERN, KERN_STACKGAPRANDOM, 0 }, + { "kern.sysvipc_info", CTL_KERN, KERN_SYSVIPC_INFO, 0 }, + { "kern.allowkmem", CTL_KERN, KERN_ALLOWKMEM, 0 }, + { "kern.witnesswatch", CTL_KERN, KERN_WITNESSWATCH, 0 }, + { "kern.splassert", CTL_KERN, KERN_SPLASSERT, 0 }, + { "kern.procargs", CTL_KERN, KERN_PROC_ARGS, 0 }, + { "kern.nfiles", CTL_KERN, KERN_NFILES, 0 }, + { "kern.ttycount", CTL_KERN, KERN_TTYCOUNT, 0 }, + { "kern.numvnodes", CTL_KERN, KERN_NUMVNODES, 0 }, + { "kern.mbstat", CTL_KERN, KERN_MBSTAT, 0 }, + { "kern.witness", CTL_KERN, KERN_WITNESS, 0 }, + { "kern.seminfo", CTL_KERN, KERN_SEMINFO, 0 }, + { "kern.shminfo", CTL_KERN, KERN_SHMINFO, 0 }, + { "kern.intrcnt", CTL_KERN, KERN_INTRCNT, 0 }, + { "kern.watchdog", CTL_KERN, KERN_WATCHDOG, 0 }, + { "kern.proc", CTL_KERN, KERN_PROC, 0 }, + { "kern.maxclusters", CTL_KERN, KERN_MAXCLUSTERS, 0 }, + { "kern.evcount", CTL_KERN, KERN_EVCOUNT, 0 }, + { "kern.timecounter", CTL_KERN, KERN_TIMECOUNTER, 0 }, + { "kern.maxlocksperuid", CTL_KERN, KERN_MAXLOCKSPERUID, 0 }, + { "kern.cp_time2", CTL_KERN, KERN_CPTIME2, 0 }, + { "kern.bufcachepercent", CTL_KERN, KERN_CACHEPCT, 0 }, + { "kern.file", CTL_KERN, KERN_FILE, 0 }, + { "kern.wxabort", CTL_KERN, KERN_WXABORT, 0 }, + { "kern.consdev", CTL_KERN, KERN_CONSDEV, 0 }, + { "kern.netlivelocks", CTL_KERN, KERN_NETLIVELOCKS, 0 }, + { "kern.pool_debug", CTL_KERN, KERN_POOL_DEBUG, 0 }, + { "kern.proc_cwd", CTL_KERN, KERN_PROC_CWD, 0 }, + { "kern.proc_nobroadcastkill", CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 }, + { "kern.proc_vmap", CTL_KERN, KERN_PROC_VMMAP, 0 }, + { "kern.global_ptrace", CTL_KERN, KERN_GLOBAL_PTRACE, 0 }, + { "kern.consbufsize", CTL_KERN, KERN_CONSBUFSIZE, 0 }, + { "kern.consbuf", CTL_KERN, KERN_CONSBUF, 0 }, + { "kern.audio", CTL_KERN, KERN_AUDIO, 0 }, + { "kern.cpustats", CTL_KERN, KERN_CPUSTATS, 0 }, + { "kern.pfstatus", CTL_KERN, KERN_PFSTATUS, 0 }, + { "kern.timeout_stats", CTL_KERN, KERN_TIMEOUT_STATS, 0 }, + { "kern.utc_offset", CTL_KERN, KERN_UTC_OFFSET, 0 }, + { "vm.vmmeter", CTL_VM, VM_METER, 0 }, + { "vm.loadavg", CTL_VM, VM_LOADAVG, 0 }, + { "vm.psstrings", CTL_VM, VM_PSSTRINGS, 0 }, + { "vm.uvmexp", CTL_VM, VM_UVMEXP, 0 }, + { "vm.swapencrypt", CTL_VM, VM_SWAPENCRYPT, 0 }, + { "vm.nkmempages", CTL_VM, VM_NKMEMPAGES, 0 }, + { "vm.anonmin", CTL_VM, VM_ANONMIN, 0 }, + { "vm.vtextmin", CTL_VM, VM_VTEXTMIN, 0 }, + { "vm.vnodemin", CTL_VM, VM_VNODEMIN, 0 }, + { "vm.maxslp", CTL_VM, VM_MAXSLP, 0 }, + { "vm.uspace", CTL_VM, VM_USPACE, 0 }, + { "vm.malloc_conf", CTL_VM, VM_MALLOC_CONF, 0 }, + { NULL, 0, 0, 0 }, +}; diff --git a/src/openbsd/sysctlbyname.cpp b/src/openbsd/sysctlbyname.cpp new file mode 100644 index 0000000..b92484a --- /dev/null +++ b/src/openbsd/sysctlbyname.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2021 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "../btop_tools.hpp" + +int +sysctlbyname(const char *name, void *oldp, size_t *oldlenp, + void *newp, size_t newlen) +{ + int i, mib[2]; + + for (i = 0; i < 132; i++) { + // for (i = 0; i < sizeof(sysctlnames) / sizeof(sysctlnames[0]); i++) { + if (!strcmp(name, sysctlnames[i].name)) { + mib[0] = sysctlnames[i].mib0; + mib[1] = sysctlnames[i].mib1; + + return sysctl(mib, 2, oldp, oldlenp, newp, newlen); + } + } + + errno = ENOENT; + + return (-1); +} diff --git a/src/openbsd/sysctlbyname.h b/src/openbsd/sysctlbyname.h new file mode 100644 index 0000000..3b02382 --- /dev/null +++ b/src/openbsd/sysctlbyname.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2019 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +extern int sysctlbyname(const char *, void *, size_t *, void *, size_t); From b8e43d92b89e8e3de66d233777df81629b7274af Mon Sep 17 00:00:00 2001 From: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:40:18 +0200 Subject: [PATCH 02/91] Use native POSIX polling syscalls to read input No more awkward manual polling --- src/btop.cpp | 28 ++++++++---- src/btop_input.cpp | 104 ++++++++++++++------------------------------- src/btop_input.hpp | 15 ++++--- src/btop_tools.cpp | 9 ++-- 4 files changed, 67 insertions(+), 89 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index e53ec02..fd75822 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -175,7 +175,7 @@ void term_resize(bool force) { static atomic resizing (false); if (Input::polling) { Global::resized = true; - Input::interrupt = true; + Input::interrupt(); return; } atomic_lock lck(resizing, true); @@ -234,7 +234,7 @@ void term_resize(bool force) { else if (not Term::refresh()) break; } - Input::interrupt = true; + Input::interrupt(); } //* Exit handler; stops threads, restores terminal and saves config changes @@ -304,7 +304,7 @@ void _signal_handler(const int sig) { if (Runner::active) { Global::should_quit = true; Runner::stopping = true; - Input::interrupt = true; + Input::interrupt(); } else { clean_quit(0); @@ -314,7 +314,7 @@ void _signal_handler(const int sig) { if (Runner::active) { Global::should_sleep = true; Runner::stopping = true; - Input::interrupt = true; + Input::interrupt(); } else { _sleep(); @@ -326,6 +326,9 @@ void _signal_handler(const int sig) { case SIGWINCH: term_resize(); break; + case SIGUSR1: + // Input::poll interrupt + break; } } @@ -451,7 +454,7 @@ namespace Runner { 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 = true; + Input::interrupt(); stopping = true; } @@ -462,7 +465,7 @@ namespace Runner { if (active) { Global::exit_error_msg = "Runner thread failed to get active lock!"; Global::thread_exception = true; - Input::interrupt = true; + Input::interrupt(); stopping = true; } if (stopping or Global::resized) { @@ -505,7 +508,7 @@ namespace Runner { coreNum_reset = false; Cpu::core_mapping = Cpu::get_core_mapping(); Global::resized = true; - Input::interrupt = true; + Input::interrupt(); continue; } @@ -584,7 +587,7 @@ namespace Runner { catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; Global::thread_exception = true; - Input::interrupt = true; + Input::interrupt(); stopping = true; } @@ -937,6 +940,12 @@ int main(int argc, char **argv) { std::signal(SIGTSTP, _signal_handler); std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); + std::signal(SIGUSR1, _signal_handler); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask); //? Start runner thread Runner::thread_sem_init(); @@ -958,9 +967,10 @@ int main(int argc, char **argv) { { 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; - Input::interrupt = false; } } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index ebdee3b..0fcb51c 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -16,12 +16,13 @@ indent = tab tab-size = 4 */ -#include +#include #include #include #include #include #include +#include #include "btop_input.hpp" #include "btop_tools.hpp" @@ -30,9 +31,6 @@ tab-size = 4 #include "btop_menu.hpp" #include "btop_draw.hpp" - -using std::cin; - using namespace Tools; using namespace std::literals; // for operator""s namespace rng = std::ranges; @@ -76,83 +74,45 @@ namespace Input { {"[24~", "f12"} }; - std::atomic interrupt (false); + sigset_t signal_mask; std::atomic polling (false); array mouse_pos; unordered_flat_map mouse_mappings; deque history(50, ""); string old_filter; + string input; - struct InputThr { - InputThr() : thr(run, this) { - } - - static void run(InputThr* that) { - that->runImpl(); - } - - void runImpl() { - char ch = 0; - - // TODO(pg83): read whole buffer - while (cin.get(ch)) { - std::lock_guard g(lock); - current.push_back(ch); - if (current.size() > 100) { - current.clear(); - } - } - } - - size_t avail() { - std::lock_guard g(lock); - - return current.size(); - } - - std::string get() { - std::string res; - - { - std::lock_guard g(lock); - - res.swap(current); - } - - return res; - } - - static InputThr& instance() { - // intentional memory leak, to simplify shutdown process - static InputThr* input = new InputThr(); - - return *input; - } - - std::string current; - // TODO(pg83): use std::conditional_variable instead of sleep - std::mutex lock; - std::thread thr; - }; - - bool poll(int timeout) { + bool poll(const uint64_t timeout) { atomic_lock lck(polling); - if (timeout < 1) return InputThr::instance().avail() > 0; - while (timeout > 0) { - if (interrupt) { - interrupt = false; - return false; - } - if (InputThr::instance().avail() > 0) return true; - sleep_ms(timeout < 10 ? timeout : 10); - timeout -= 10; + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + struct timespec wait; + struct timespec *waitptr = nullptr; + + if(timeout != std::numeric_limits::max()) { + wait.tv_sec = timeout / 1000; + wait.tv_nsec = (timeout % 1000) * 1000000; + waitptr = &wait; } + + if(pselect(STDIN_FILENO + 1, &fds, nullptr, nullptr, waitptr, &signal_mask) > 0) { + input.clear(); + char buf[1024]; + ssize_t count = 0; + while((count = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + input.append(std::string_view(buf, count)); + } + + return true; + } + return false; } string get() { - string key = InputThr::instance().get(); + string key = input; if (not key.empty()) { //? Remove escape code prefix if present if (key.substr(0, 2) == Fx::e) { @@ -225,12 +185,14 @@ namespace Input { } string wait() { - while (InputThr::instance().avail() < 1) { - sleep_ms(10); - } + while(not poll(std::numeric_limits::max())) {} return get(); } + void interrupt() { + kill(getpid(), SIGUSR1); + } + void clear() { // do not need it, actually } diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..5af8b28 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -30,9 +30,9 @@ using std::atomic; using std::deque; using std::string; -/* The input functions relies on the following std::cin options being set: - cin.sync_with_stdio(false); - cin.tie(nullptr); +/* The input functions rely on the following termios parameters being set: + Non-canonical mode (c_lflags & ~(ICANON)) + VMIN and VTIME (c_cc) set to 0 These will automatically be set when running Term::init() from btop_tools.cpp */ @@ -46,7 +46,9 @@ namespace Input { //? line, col, height, width extern unordered_flat_map mouse_mappings; - extern atomic interrupt; + //* Signal mask used during polling read + extern sigset_t signal_mask; + extern atomic polling; //* Mouse column and line position @@ -56,7 +58,7 @@ namespace Input { extern deque history; //* Poll keyboard & mouse input for ms and return input availabilty as a bool - bool poll(int timeout=0); + bool poll(const uint64_t timeout=0); //* Get a key or mouse action from input string get(); @@ -64,6 +66,9 @@ namespace Input { //* Wait until input is available and return key string wait(); + //* Interrupt poll/wait + void interrupt(); + //* Clears last entered key void clear(); diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index f6be174..4a74107 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -36,7 +36,6 @@ tab-size = 4 #include "btop_tools.hpp" #include "btop_config.hpp" -using std::cin; using std::cout; using std::floor; using std::flush; @@ -77,7 +76,11 @@ namespace Term { struct termios settings; if (tcgetattr(STDIN_FILENO, &settings)) return false; if (on) settings.c_lflag |= ICANON; - else settings.c_lflag &= ~(ICANON); + else { + settings.c_lflag &= ~(ICANON); + settings.c_cc[VMIN] = 0; + settings.c_cc[VTIME] = 0; + } if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false; if (on) setlinebuf(stdin); else setbuf(stdin, nullptr); @@ -124,11 +127,9 @@ namespace Term { current_tty = (ttyname(STDIN_FILENO) != nullptr ? static_cast(ttyname(STDIN_FILENO)) : "unknown"); //? Disable stream sync - cin.sync_with_stdio(false); cout.sync_with_stdio(false); //? Disable stream ties - cin.tie(nullptr); cout.tie(nullptr); echo(false); linebuffered(false); From c90e3f9b1508f55d281495468297df409f974eb0 Mon Sep 17 00:00:00 2001 From: nobounce Date: Fri, 22 Sep 2023 20:58:21 +0200 Subject: [PATCH 03/91] Try get terminal size of "/dev/tty" if stdout fails LLDB does not set the terminal sizes for stdout so try to get them for "/dev/tty". This helps to get back traces on arm64 where gdb is not supported --- src/btop_tools.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index f6be174..d3546f3 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -26,9 +26,10 @@ tab-size = 4 #include #include -#include -#include +#include #include +#include +#include #include "robin_hood.h" #include "widechar_width.hpp" @@ -86,12 +87,27 @@ namespace Term { } bool refresh(bool only_check) { - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) return false; - if (width != w.ws_col or height != w.ws_row) { + // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't avaiable. + // This variable is set in those cases to avoid calls to ioctl + constinit static bool uses_dev_tty = false; + struct winsize wsize {}; + if (uses_dev_tty || ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsize) < 0 || (wsize.ws_col == 0 && wsize.ws_row == 0)) { + Logger::error(R"(Couldn't determine terminal size of "STDOUT_FILENO"!)"); + auto dev_tty = open("/dev/tty", O_RDONLY); + if (dev_tty != -1) { + ioctl(dev_tty, TIOCGWINSZ, &wsize); + close(dev_tty); + } + else { + Logger::error(R"(Couldn't determine terminal size of "/dev/tty"!)"); + return false; + } + uses_dev_tty = true; + } + if (width != wsize.ws_col or height != wsize.ws_row) { if (not only_check) { - width = w.ws_col; - height = w.ws_row; + width = wsize.ws_col; + height = wsize.ws_row; } return true; } From 4c74a7c51dba31f43742cb3c1dd9b139ffbbbed6 Mon Sep 17 00:00:00 2001 From: jkre Date: Wed, 13 Dec 2023 22:51:40 +0100 Subject: [PATCH 04/91] Add current_now and voltage_now to battery struct and fix naming of current_now --- src/linux/btop_collect.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 33a0be8..68b8c58 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -661,9 +661,10 @@ namespace Cpu { } struct battery { - fs::path base_dir, energy_now, energy_full, power_now, status, online; + fs::path base_dir, energy_now, energy_full, power_now, current_now, voltage_now, status, online; string device_type; bool use_energy = true; + bool use_power = true; }; auto get_battery() -> tuple { @@ -710,8 +711,16 @@ namespace Cpu { continue; } - if (fs::exists(bat_dir / "power_now")) new_bat.power_now = bat_dir / "power_now"; - else if (fs::exists(bat_dir / "current_now")) new_bat.power_now = bat_dir / "current_now"; + if (fs::exists(bat_dir / "power_now")) { + new_bat.power_now = bat_dir / "power_now"; + } + else if ((fs::exists(bat_dir / "current_now")) and (fs::exists(bat_dir / "current_now"))) { + new_bat.current_now = bat_dir / "current_now"; + new_bat.voltage_now = bat_dir / "voltage_now"; + } + else { + new_bat.use_power = false; + } if (fs::exists(bat_dir / "AC0/online")) new_bat.online = bat_dir / "AC0/online"; else if (fs::exists(bat_dir / "AC/online")) new_bat.online = bat_dir / "AC/online"; From b99008f6267ebbcd700052d5349f4c4bac8b08c3 Mon Sep 17 00:00:00 2001 From: jkre Date: Wed, 13 Dec 2023 23:17:07 +0100 Subject: [PATCH 05/91] Introduce charge in addition to energy for laptops that use charge instead of energy, this is done so that the units make more sense in this case --- src/linux/btop_collect.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 68b8c58..1da9c4a 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -661,9 +661,9 @@ namespace Cpu { } struct battery { - fs::path base_dir, energy_now, energy_full, power_now, current_now, voltage_now, status, online; + fs::path base_dir, energy_now, charge_now, energy_full, charge_full, power_now, current_now, voltage_now, status, online; string device_type; - bool use_energy = true; + bool use_energy_or_charge = true; bool use_power = true; }; @@ -700,14 +700,14 @@ namespace Cpu { } if (fs::exists(bat_dir / "energy_now")) new_bat.energy_now = bat_dir / "energy_now"; - else if (fs::exists(bat_dir / "charge_now")) new_bat.energy_now = bat_dir / "charge_now"; - else new_bat.use_energy = false; + else if (fs::exists(bat_dir / "charge_now")) new_bat.charge_now = bat_dir / "charge_now"; + else new_bat.use_energy_or_charge = false; if (fs::exists(bat_dir / "energy_full")) new_bat.energy_full = bat_dir / "energy_full"; - else if (fs::exists(bat_dir / "charge_full")) new_bat.energy_full = bat_dir / "charge_full"; - else new_bat.use_energy = false; + else if (fs::exists(bat_dir / "charge_full")) new_bat.charge_full = bat_dir / "charge_full"; + else new_bat.use_energy_or_charge = false; - if (not new_bat.use_energy and not fs::exists(bat_dir / "capacity")) { + if (not new_bat.use_energy_or_charge and not fs::exists(bat_dir / "capacity")) { continue; } @@ -757,7 +757,7 @@ namespace Cpu { long seconds = -1; //? Try to get battery percentage - if (b.use_energy) { + if (b.use_energy_or_charge) { try { percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1"))); } @@ -787,7 +787,7 @@ namespace Cpu { //? Get seconds to empty if (not is_in(status, "charging", "full")) { - if (b.use_energy and not b.power_now.empty()) { + if (b.use_energy_or_charge and not b.power_now.empty()) { try { seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600); } From ee61700a4437afffd019264c50ad2a18cab75062 Mon Sep 17 00:00:00 2001 From: jkre Date: Wed, 13 Dec 2023 23:39:45 +0100 Subject: [PATCH 06/91] add case of calculating the remaining battery time for current/charge --- src/linux/btop_collect.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 1da9c4a..ae11f61 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -787,13 +787,23 @@ namespace Cpu { //? Get seconds to empty if (not is_in(status, "charging", "full")) { - if (b.use_energy_or_charge and not b.power_now.empty()) { - try { - seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600); + if (b.use_energy_or_charge ) { + if (not b.power_now.empty()) { + try { + seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + else if (not b.current_now.empty()) { + try { + seconds = round((double)stoll(readfile(b.charge_now, "0")) / (double)stoll(readfile(b.current_now, "1")) * 3600); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } } - catch (const std::invalid_argument&) { } - catch (const std::out_of_range&) { } } + if (seconds < 0 and fs::exists(b.base_dir / "time_to_empty")) { try { seconds = stoll(readfile(b.base_dir / "time_to_empty", "0")) * 60; From ab294bfc104d20e1178cb4c6bc4de2fd879cb892 Mon Sep 17 00:00:00 2001 From: jkre Date: Wed, 13 Dec 2023 23:51:18 +0100 Subject: [PATCH 07/91] add battery percentage calculation in charge case --- src/linux/btop_collect.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index ae11f61..0d58596 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -764,6 +764,13 @@ namespace Cpu { catch (const std::invalid_argument&) { } catch (const std::out_of_range&) { } } + if (b.use_energy_or_charge == true and percent < 0) { + try { + percent = round(100.0 * stoll(readfile(b.charge_now, "-1")) / stoll(readfile(b.charge_full, "1"))); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } if (percent < 0) { try { percent = stoll(readfile(b.base_dir / "capacity", "-1")); From f6d8c4a0447a7bb56f8717a6f21920bc6d7ba310 Mon Sep 17 00:00:00 2001 From: jkre Date: Wed, 13 Dec 2023 23:58:40 +0100 Subject: [PATCH 08/91] use capacity as default for battery percentage, less complicated and matches desktop percent exactly --- src/linux/btop_collect.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0d58596..3961d91 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -757,7 +757,14 @@ namespace Cpu { long seconds = -1; //? Try to get battery percentage - if (b.use_energy_or_charge) { + if (percent < 0) { + try { + percent = stoll(readfile(b.base_dir / "capacity", "-1")); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + if (b.use_energy_or_charge and percent < 0) { try { percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1"))); } @@ -771,13 +778,6 @@ namespace Cpu { catch (const std::invalid_argument&) { } catch (const std::out_of_range&) { } } - if (percent < 0) { - try { - percent = stoll(readfile(b.base_dir / "capacity", "-1")); - } - catch (const std::invalid_argument&) { } - catch (const std::out_of_range&) { } - } if (percent < 0) { has_battery = false; return {0, 0, ""}; From b09f352c09c14788985e23cd01f2975b2443b810 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 00:16:15 +0100 Subject: [PATCH 09/91] clean up if statement for battery percent calculation --- src/linux/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 3961d91..212a165 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -771,7 +771,7 @@ namespace Cpu { catch (const std::invalid_argument&) { } catch (const std::out_of_range&) { } } - if (b.use_energy_or_charge == true and percent < 0) { + if (b.use_energy_or_charge and percent < 0) { try { percent = round(100.0 * stoll(readfile(b.charge_now, "-1")) / stoll(readfile(b.charge_full, "1"))); } From 419a7d4ca3889d97afc006f7301017a224e16396 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 00:32:06 +0100 Subject: [PATCH 10/91] add power draw calculation for battery --- src/linux/btop_collect.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 212a165..c00c563 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -755,6 +755,7 @@ namespace Cpu { int percent = -1; long seconds = -1; + float watts = -1; //? Try to get battery percentage if (percent < 0) { @@ -820,6 +821,25 @@ namespace Cpu { } } + //? Get power draw + if (b.use_power) { + if (not b.power_now.empty()) { + try { + watts = (float)stoll(readfile(b.energy_now, "-1")) / 1000000.0; + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + else if (not b.voltage_now.empty() and not b.current_now.empty()) { + try { + watts = (float)stoll(readfile(b.current_now, "-1")) / 1000000.0 * stoll(readfile(b.voltage_now, "1")) / 1000000.0; + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + + } + return {percent, seconds, status}; } From 6e575116fe45b0a48f873820bc9fe4486b827eed Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 00:53:01 +0100 Subject: [PATCH 11/91] add power to get_battery function output --- src/btop_shared.hpp | 4 ++-- src/linux/btop_collect.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 017c7f8..eb5514c 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -178,7 +178,7 @@ namespace Cpu { extern string cpuName, cpuHz; extern vector available_fields; extern vector available_sensors; - extern tuple current_bat; + extern tuple current_bat; struct cpu_info { unordered_flat_map> cpu_percent = { @@ -213,7 +213,7 @@ namespace Cpu { auto get_cpuHz() -> string; //* Get battery info from /sys - auto get_battery() -> tuple; + auto get_battery() -> tuple; } namespace Mem { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index c00c563..d88454a 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -291,7 +291,7 @@ namespace Cpu { string cpuName; string cpuHz; bool has_battery = true; - tuple current_bat; + tuple current_bat; const array time_names { "user"s, "nice"s, "system"s, "idle"s, "iowait"s, @@ -667,8 +667,8 @@ namespace Cpu { bool use_power = true; }; - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; static string auto_sel; static unordered_flat_map batteries; @@ -735,7 +735,7 @@ namespace Cpu { } if (batteries.empty()) { has_battery = false; - return {0, 0, ""}; + return {0, 0, 0, ""}; } } @@ -781,7 +781,7 @@ namespace Cpu { } if (percent < 0) { has_battery = false; - return {0, 0, ""}; + return {0, 0, 0, ""}; } //? Get charging/discharging status @@ -840,7 +840,7 @@ namespace Cpu { } - return {percent, seconds, status}; + return {percent, watts, seconds, status}; } auto collect(bool no_update) -> cpu_info& { From ddd4bec1c3eed40241fe22254770bc52e931bb88 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 00:54:29 +0100 Subject: [PATCH 12/91] Show wattage next to battery remaining time when wattage could be calculated --- src/btop_draw.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 5ae357e..dde135d 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -705,6 +705,7 @@ namespace Cpu { if (Config::getB("show_battery") and has_battery) { static int old_percent{}; // defaults to = 0 static long old_seconds{}; // defaults to = 0 + static float old_watts{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; static const unordered_flat_map bat_symbols = { @@ -714,16 +715,18 @@ namespace Cpu { {"unknown", "○"} }; - const auto& [percent, seconds, status] = current_bat; + const auto& [percent, watts, seconds, status] = current_bat; - if (redraw or percent != old_percent or seconds != old_seconds or status != old_status) { + if (redraw or percent != old_percent or watts != old_watts or seconds != old_seconds or status != old_status) { old_percent = percent; + old_watts = watts; old_seconds = seconds; old_status = status; const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : ""); const string str_percent = to_string(percent) + '%'; + const string str_watts = (watts != -1 ? to_string(watts) + 'W' : ""); const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); - const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + to_string(Config::getI("update_ms")).size(); + const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + str_watts.size() + to_string(Config::getI("update_ms")).size(); const int current_pos = Term::width - current_len - 17; if ((bat_pos != current_pos or bat_len != current_len) and bat_pos > 0 and not redraw) @@ -733,7 +736,7 @@ namespace Cpu { out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent + (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "") - + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : " ") + Fx::ub + title_right; + + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : " ") + (not str_watts.empty() ? ' ' + Theme::c("title") + Fx::b + str_watts : " ") + Fx::ub + title_right; } } else if (bat_pos > 0) { From 1ad1418771bd69c70674388d64a4965f9f7d0582 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 00:58:17 +0100 Subject: [PATCH 13/91] remove redundant space --- src/btop_draw.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index dde135d..b5e75ec 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -736,7 +736,7 @@ namespace Cpu { out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent + (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "") - + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : " ") + (not str_watts.empty() ? ' ' + Theme::c("title") + Fx::b + str_watts : " ") + Fx::ub + title_right; + + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : "") + (not str_watts.empty() ? ' ' + Theme::c("title") + Fx::b + str_watts : " ") + Fx::ub + title_right; } } else if (bat_pos > 0) { @@ -2239,3 +2239,4 @@ namespace Draw { } } } + From 7a188bfaaf19266b2d74336ca85ccb46fa0f111a Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 01:39:00 +0100 Subject: [PATCH 14/91] round wattage to second decimal --- src/btop_draw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index b5e75ec..9e49d1f 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -724,7 +724,7 @@ namespace Cpu { old_status = status; const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : ""); const string str_percent = to_string(percent) + '%'; - const string str_watts = (watts != -1 ? to_string(watts) + 'W' : ""); + const string str_watts = (watts != -1 ? fmt::format("{:.2f}", watts) + 'W' : ""); const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + str_watts.size() + to_string(Config::getI("update_ms")).size(); const int current_pos = Term::width - current_len - 17; From 578b01e06b6e9bc94d6b20ec361a518ab466ec96 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 22:19:22 +0100 Subject: [PATCH 15/91] add show_battery_power option to menu --- src/btop_config.cpp | 3 +++ src/btop_menu.cpp | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 0f723b7..454429b 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -197,6 +197,8 @@ namespace Config { {"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, + {"show_battery_watt" "#* Show power stats of battery next to charge indicator"}, + {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, #ifdef GPU_SUPPORT @@ -291,6 +293,7 @@ namespace Config { {"net_auto", true}, {"net_sync", true}, {"show_battery", true}, + {"show_battery_watts", true}, {"vim_keys", false}, {"tty_mode", false}, {"disk_free_priv", false}, diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 206052f..5bbd524 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -354,6 +354,13 @@ namespace Menu { "Can be both batteries and UPS.", "", "\"Auto\" for auto detection."}, + {"show_battery_watts", + "Show battery power.", + "" + "Shows power consumed by device when not connected to power", + "Shows power charging power otherwise" + "", + "True or False."}, {"log_level", "Set loglevel for error.log", "", From 2934138a660ad0b80bde31e5c81f495d7b76dc13 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 22:56:31 +0100 Subject: [PATCH 16/91] Only redraw battery indicator on power change if power change option is set to true --- src/btop_draw.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 9e49d1f..ae9ba69 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -717,14 +717,14 @@ namespace Cpu { const auto& [percent, watts, seconds, status] = current_bat; - if (redraw or percent != old_percent or watts != old_watts or seconds != old_seconds or status != old_status) { + if (redraw or percent != old_percent or (watts != old_watts and Config::getB("show_battery_watts")) or seconds != old_seconds or status != old_status) { old_percent = percent; old_watts = watts; old_seconds = seconds; old_status = status; const string str_time = (seconds > 0 ? sec_to_dhms(seconds, true, true) : ""); const string str_percent = to_string(percent) + '%'; - const string str_watts = (watts != -1 ? fmt::format("{:.2f}", watts) + 'W' : ""); + const string str_watts = (watts != -1 and Config::getB("show_battery_watts") ? fmt::format("{:.2f}", watts) + 'W' : ""); const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + str_watts.size() + to_string(Config::getI("update_ms")).size(); const int current_pos = Term::width - current_len - 17; @@ -736,7 +736,7 @@ namespace Cpu { out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent + (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "") - + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : "") + (not str_watts.empty() ? ' ' + Theme::c("title") + Fx::b + str_watts : " ") + Fx::ub + title_right; + + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : "") + (not str_watts.empty() ? " " + Theme::c("title") + Fx::b + str_watts : "") + Fx::ub + title_right; } } else if (bat_pos > 0) { From cd6c1b7294cfdabe234da13c45ba6613ed552eb5 Mon Sep 17 00:00:00 2001 From: jkre Date: Thu, 14 Dec 2023 23:27:19 +0100 Subject: [PATCH 17/91] make discribtion in menu and settings clearer --- src/btop_config.cpp | 2 +- src/btop_menu.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 454429b..5fed33f 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -197,7 +197,7 @@ namespace Config { {"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, - {"show_battery_watt" "#* Show power stats of battery next to charge indicator"}, + {"show_battery_watts", "#* Show power stats of battery next to charge indicator."}, {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 5bbd524..5d1421f 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -356,11 +356,9 @@ namespace Menu { "\"Auto\" for auto detection."}, {"show_battery_watts", "Show battery power.", - "" - "Shows power consumed by device when not connected to power", - "Shows power charging power otherwise" "", - "True or False."}, + "Show discharge power when discharging.", + "Show charging power when charging."}, {"log_level", "Set loglevel for error.log", "", From a81b514d6df6eaff0293aa4ce869a429a1cde4b2 Mon Sep 17 00:00:00 2001 From: jkre Date: Fri, 15 Dec 2023 01:14:47 +0100 Subject: [PATCH 18/91] add freebsd support for battery power --- src/freebsd/btop_collect.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 7f93322..d678038 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -200,7 +200,7 @@ namespace Cpu { string cpuName; string cpuHz; bool has_battery = true; - tuple current_bat; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle"}; @@ -366,10 +366,11 @@ namespace Cpu { return core_map; } - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; long seconds = -1; + float watts = -1; uint32_t percent = -1; size_t size = sizeof(percent); string status = "discharging"; @@ -381,6 +382,10 @@ namespace Cpu { if (sysctlbyname("hw.acpi.battery.time", &seconds, &size, nullptr, 0) < 0) { seconds = 0; } + size = sizeof(watts); + if (sysctlbyname("hw.acpi.battery.rate", &watts, &size, nullptr, 0) < 0) { + watts = -1; + } int state; size = sizeof(state); if (sysctlbyname("hw.acpi.battery.state", &state, &size, nullptr, 0) < 0) { @@ -395,7 +400,7 @@ namespace Cpu { } } - return {percent, seconds, status}; + return {percent, watts, seconds, status}; } auto collect(bool no_update) -> cpu_info & { From 0c706cd20a262531788469ca432ab0a09d75c852 Mon Sep 17 00:00:00 2001 From: jkre Date: Fri, 15 Dec 2023 01:28:57 +0100 Subject: [PATCH 19/91] make os compatible --- src/osx/btop_collect.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index e410c74..118af83 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -187,7 +187,7 @@ namespace Cpu { string cpuHz; bool has_battery = true; bool macM1 = false; - tuple current_bat; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle"}; @@ -398,8 +398,8 @@ namespace Cpu { ~IOPSList_Wrap() { CFRelease(data); } }; - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; uint32_t percent = -1; long seconds = -1; @@ -438,7 +438,7 @@ namespace Cpu { has_battery = false; } } - return {percent, seconds, status}; + return {percent, -1, seconds, status}; } auto collect(bool no_update) -> cpu_info & { From 780524267fe16b11638ca002d636c5feb8cbd5b2 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Fri, 15 Dec 2023 09:02:57 +0100 Subject: [PATCH 20/91] conditional compile on Big Sur and up --- src/osx/btop_collect.cpp | 10 +++++++++- src/osx/sensors.cpp | 3 +++ src/osx/sensors.hpp | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index e410c74..681afc4 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -56,7 +57,9 @@ tab-size = 4 #include "../btop_shared.hpp" #include "../btop_tools.hpp" +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" +#endif #include "smc.hpp" using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; @@ -250,6 +253,7 @@ namespace Cpu { Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp"))); got_sensors = false; if (Config::getB("show_coretemp") and Config::getB("check_temp")) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; if (sensors.getSensors() > 0) { Logger::debug("M1 sensors found"); @@ -257,6 +261,7 @@ namespace Cpu { cpu_temp_only = true; macM1 = true; } else { +#endif // try SMC (intel) Logger::debug("checking intel"); SMCConnection smcCon; @@ -281,7 +286,9 @@ namespace Cpu { // ignore, we don't have temp got_sensors = false; } +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 } +#endif } return got_sensors; } @@ -290,11 +297,12 @@ namespace Cpu { current_cpu.temp_max = 95; // we have no idea how to get the critical temp try { if (macM1) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; current_cpu.temp.at(0).push_back(sensors.getSensors()); if (current_cpu.temp.at(0).size() > 20) current_cpu.temp.at(0).pop_front(); - +#endif } else { SMCConnection smcCon; int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount; diff --git a/src/osx/sensors.cpp b/src/osx/sensors.cpp index 965d9a9..bee8978 100644 --- a/src/osx/sensors.cpp +++ b/src/osx/sensors.cpp @@ -16,6 +16,8 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" #include @@ -109,3 +111,4 @@ long long Cpu::ThermalSensors::getSensors() { if (temps.empty()) return 0ll; return round(std::accumulate(temps.begin(), temps.end(), 0ll) / temps.size()); } +#endif diff --git a/src/osx/sensors.hpp b/src/osx/sensors.hpp index 08581e1..fcb28b0 100644 --- a/src/osx/sensors.hpp +++ b/src/osx/sensors.hpp @@ -16,9 +16,12 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 namespace Cpu { class ThermalSensors { public: long long getSensors(); }; } // namespace Cpu +#endif From aab2e8cc552c752cbb15675c8ab96e1bd785ad3b Mon Sep 17 00:00:00 2001 From: "Jakob P. Liljenberg" Date: Sun, 17 Dec 2023 12:03:47 +0100 Subject: [PATCH 21/91] Fixed test-snap-can-build.yml --- .github/workflows/test-snap-can-build.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-snap-can-build.yml b/.github/workflows/test-snap-can-build.yml index fde536f..c66df73 100644 --- a/.github/workflows/test-snap-can-build.yml +++ b/.github/workflows/test-snap-can-build.yml @@ -1,19 +1,27 @@ name: 🧪 Test snap can be built on x86_64 on: + workflow_dispatch: push: - branches: [ main ] + branches: [ main ] tags-ignore: - '*.*' - paths: + paths: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' - 'include/**' - 'Makefile' - - '.github/workflows/continuous-build-linux.yml' - pull_request: - branches: [ main ] + - '.github/workflows/test-snap-can-build.yml' + pull_request: + branches: [ main ] + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/test-snap-can-build.yml' jobs: build: From 6c87ab61969f028e49ad596139cff53eb6c3becc Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 02:16:15 +0100 Subject: [PATCH 22/91] Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback --- Makefile | 8 +- src/btop.cpp | 3 +- src/btop_config.cpp | 13 +-- src/btop_config.hpp | 15 ++- src/btop_draw.cpp | 172 +++++++++++++++++------------------ src/btop_draw.hpp | 9 +- src/btop_input.cpp | 4 +- src/btop_input.hpp | 5 +- src/btop_menu.cpp | 7 +- src/btop_menu.hpp | 2 +- src/btop_shared.cpp | 2 +- src/btop_shared.hpp | 29 +++--- src/btop_theme.cpp | 14 +-- src/btop_theme.hpp | 9 +- src/btop_tools.cpp | 3 +- src/btop_tools.hpp | 106 +++++++++++++++------ src/freebsd/btop_collect.cpp | 22 ++--- src/linux/btop_collect.cpp | 30 +++--- src/osx/btop_collect.cpp | 22 ++--- 19 files changed, 261 insertions(+), 214 deletions(-) diff --git a/Makefile b/Makefile index 970e818..2b02f3d 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,10 @@ CLANG_WORKS = false GCC_WORKS = false MIN_CLANG_VERSION = 16 +ifeq ($(DEBUG),true) + override ADDFLAGS += -DBTOP_DEBUG +endif + #? Supported is Clang 16.0.0 and later ifeq ($(CXX_IS_CLANG),true) ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) @@ -279,13 +283,13 @@ directories: clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) - @cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true + @test -e lib/rocm_smi_lib/build && cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true #? Clean Objects and Binaries distclean: clean @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @rm -rf $(TARGETDIR) - @rm -rf lib/rocm_smi_lib/build + @test -e lib/rocm_smi_lib/build && rm -rf lib/rocm_smi_lib/build || true install: @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" diff --git a/src/btop.cpp b/src/btop.cpp index 252f2a9..8a0eb05 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -32,6 +32,7 @@ tab-size = 4 #include #include #include +#include #ifdef __APPLE__ #include #include @@ -416,7 +417,7 @@ namespace Runner { }; string debug_bg; - unordered_flat_map> debug_times; + std::unordered_map> debug_times; class MyNumPunct : public std::numpunct { diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 0f723b7..6ddfd43 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,6 +21,7 @@ tab-size = 4 #include #include #include +#include #include @@ -215,7 +216,7 @@ namespace Config { #endif }; - unordered_flat_map strings = { + std::unordered_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, @@ -251,9 +252,9 @@ namespace Config { {"show_gpu_info", "Auto"} #endif }; - unordered_flat_map stringsTmp; + std::unordered_map stringsTmp; - unordered_flat_map bools = { + std::unordered_map bools = { {"theme_background", true}, {"truecolor", true}, {"rounded_corners", true}, @@ -304,9 +305,9 @@ namespace Config { {"gpu_mirror_graph", true}, #endif }; - unordered_flat_map boolsTmp; + std::unordered_map boolsTmp; - unordered_flat_map ints = { + std::unordered_map ints = { {"update_ms", 2000}, {"net_download", 100}, {"net_upload", 100}, @@ -317,7 +318,7 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - unordered_flat_map intsTmp; + std::unordered_map intsTmp; bool _locked(const std::string_view name) { atomic_wait(writelock, true); diff --git a/src/btop_config.hpp b/src/btop_config.hpp index c71d29c..2b586af 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -22,11 +22,10 @@ tab-size = 4 #include #include -#include +#include using std::string; using std::vector; -using robin_hood::unordered_flat_map; //* Functions and variables for reading and writing the btop config file namespace Config { @@ -34,12 +33,12 @@ namespace Config { extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern unordered_flat_map strings; - extern unordered_flat_map stringsTmp; - extern unordered_flat_map bools; - extern unordered_flat_map boolsTmp; - extern unordered_flat_map ints; - extern unordered_flat_map intsTmp; + extern std::unordered_map strings; + extern std::unordered_map stringsTmp; + extern std::unordered_map bools; + extern std::unordered_map boolsTmp; + extern std::unordered_map ints; + extern std::unordered_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 5ae357e..6f790eb 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -22,6 +22,7 @@ tab-size = 4 #include #include #include +#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -54,7 +55,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const unordered_flat_map> graph_symbols = { + const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -300,7 +301,7 @@ namespace Draw { return false; } - static const unordered_flat_map clock_custom_format = { + static const std::unordered_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} @@ -560,12 +561,12 @@ namespace Cpu { const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; string out; out.reserve(width * height); @@ -608,7 +609,7 @@ namespace Cpu { if (gpu.supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpu.supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -617,12 +618,12 @@ namespace Cpu { bool utilization_support = gpu.supported_functions.gpu_utilization; if (++i < gpus.size()) { if (utilization_support) - graph = Draw::Graph{graph_width, graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true}; + graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true}; } else { if (utilization_support) graph = Draw::Graph{ graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, - graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true + graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true }; break; } @@ -630,7 +631,7 @@ namespace Cpu { } else { graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", Gpu::shared_gpu_percent.at(graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true }; gpu_temp_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); @@ -638,7 +639,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -649,7 +650,7 @@ namespace Cpu { #endif graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", cpu.cpu_percent.at(graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true }; #ifdef GPU_SUPPORT if (std::cmp_less(Gpu::shown, gpus.size())) { gpu_temp_graphs.resize(gpus.size()); @@ -659,7 +660,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -692,10 +693,10 @@ namespace Cpu { if (show_temps) { temp_graphs.clear(); - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23); } } } @@ -707,7 +708,7 @@ namespace Cpu { static long old_seconds{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; - static const unordered_flat_map bat_symbols = { + static const std::unordered_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, @@ -749,7 +750,7 @@ namespace Cpu { if (graph_field.starts_with("gpu")) if (graph_field.find("totals") != string::npos) for (unsigned long i = 0;;) { - out += graphs[i](gpus[i].gpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw)); if (gpus.size() > 1) { auto i_str = to_string(i); out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str @@ -761,13 +762,13 @@ namespace Cpu { else break; } else - out += graphs[0](Gpu::shared_gpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw)); else #else (void)graph_height; (void)graph_width; #endif - out += graphs[0](cpu.cpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); }; draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); @@ -792,14 +793,14 @@ namespace Cpu { out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) - + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) + + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { - const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1 or b_columns > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color - + temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); + + safeVal(temp_graphs, 0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; @@ -814,17 +815,17 @@ namespace Cpu { + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + + safeVal(core_graphs, n)(safeVal(cpu.core_percent, n), data_same or redraw); - out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); - out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; + out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); + out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { - const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) - + temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); + + safeVal(temp_graphs, n+1)(safeVal(cpu.temp, n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -882,14 +883,14 @@ namespace Cpu { } if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); if (gpus[i].supported_functions.gpu_utilization) { - string meter = gpu_meters[i](gpus[i].gpu_percent.at("gpu-totals").back()); + string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()); out += (meter.size() > 1 ? " " : "") + meter - + Theme::g("cpu").at(gpus[i].gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpus[i].gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; } else out += Mv::r(gpu_meter_width); if (gpus[i].supported_functions.mem_used) { - out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(gpus[i].gpu_percent.at("gpu-vram-totals").back()) - + gpu_mem_graphs[i](gpus[i].gpu_percent.at("gpu-vram-totals"), data_same or redraw) + Theme::c("main_fg") + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back()) + + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg") + rjust(floating_humanizer(gpus[i].mem_used, true), 5); if (gpus[i].supported_functions.mem_total) out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); @@ -967,12 +968,12 @@ namespace Gpu { out += box[index]; if (gpu.supported_functions.gpu_utilization) { - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", gpu.gpu_percent.at("gpu-totals"), graph_symbol, false, true}; // TODO cpu -> gpu + graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu if (not single_graph) { graph_lower = Draw::Graph{ x + width - b_width - 3, graph_low_height, "cpu", - gpu.gpu_percent.at("gpu-totals"), + safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, Config::getB("cpu_invert_lower"), true }; @@ -986,7 +987,7 @@ namespace Gpu { if (gpu.supported_functions.mem_utilization) mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol}; + mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; } @@ -994,12 +995,12 @@ namespace Gpu { //? Gpu graph, meter & clock speed if (gpu.supported_functions.gpu_utilization) { - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); if (not single_graph) - out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); + out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(gpu.gpu_percent.at("gpu-totals").back()) - + Theme::g("cpu").at(gpu.gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpu.gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back()) + + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; //? Temperature graph, I assume the device supports utilization if it supports temperature if (show_temps) { @@ -1019,10 +1020,10 @@ namespace Gpu { //? Power usage meter, power state if (gpu.supported_functions.pwr_usage) { - out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(gpu.gpu_percent.at("gpu-pwr-totals").back()) - + Theme::g("cached").at(gpu.gpu_percent.at("gpu-pwr-totals").back()) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; + out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back()) + + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card - out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(gpu.pwr_state) + to_string(gpu.pwr_state); + out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); } if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { @@ -1038,9 +1039,9 @@ namespace Gpu { + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right - + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(gpu.gpu_percent.at("gpu-vram-totals"), (data_same or redraw[index])) + + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index])) + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub - + Mv::r(3) + rjust(to_string(gpu.gpu_percent.at("gpu-vram-totals").back()), 3) + '%'; + + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; //? Memory utilization if (gpu.supported_functions.mem_utilization) @@ -1096,11 +1097,11 @@ namespace Mem { int disks_io_half = 0; bool shown = true, redraw = true; string box; - unordered_flat_map mem_meters; - unordered_flat_map mem_graphs; - unordered_flat_map disk_meters_used; - unordered_flat_map disk_meters_free; - unordered_flat_map io_graphs; + std::unordered_map mem_meters; + std::unordered_map mem_graphs; + std::unordered_map disk_meters_used; + std::unordered_map disk_meters_free; + std::unordered_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; @@ -1132,14 +1133,14 @@ namespace Mem { for (const auto& name : mem_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -1148,7 +1149,7 @@ namespace Mem { //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { - unordered_flat_map custom_speeds; + std::unordered_map custom_speeds; int half_height = 0; if (io_mode) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); @@ -1230,7 +1231,7 @@ namespace Mem { if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; cy += 1; } - out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8) + out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8) + Theme::c("main_fg") + Fx::ub; cy += 1; title = "Used"; @@ -1239,13 +1240,13 @@ namespace Mem { title = "Free"; if (title.empty()) title = capitalize(name); - const string humanized = floating_humanizer(mem.stats.at(name)); + const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); + const string graphics = (use_graphs ? safeVal(mem_graphs, name)(safeVal(mem.percent, name), redraw or data_same) : safeVal(mem_meters, name)(safeVal(mem.percent, name).back())); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) - + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); + + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4); cy += (graph_height == 0 ? 2 : graph_height + 1); } else { @@ -1268,7 +1269,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); if (disk.io_read.empty()) continue; const string total = floating_humanizer(disk.total, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) @@ -1278,14 +1279,14 @@ namespace Mem { out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) - + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) + (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW"); if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount)({comb_val}, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized; cy += disks_io_h; } @@ -1293,8 +1294,8 @@ namespace Mem { const string human_read = (disk.io_read.back() > 0 ? "▲" + floating_humanizer(disk.io_read.back(), true) : "R"); const string human_write = (disk.io_write.back() > 0 ? "▼" + floating_humanizer(disk.io_write.back(), true) : "W"); if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) - + Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) + + Mv::d(1) + safeVal(io_graphs, mount + "_write")(disk.io_write, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write; cy += disks_io_h; } @@ -1304,7 +1305,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); @@ -1319,18 +1320,18 @@ namespace Mem { if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) - + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' - + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); + + safeVal(disk_meters_used, mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' - + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); + + safeVal(disk_meters_free, mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } @@ -1353,7 +1354,7 @@ namespace Net { int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; string old_ip; - unordered_flat_map graphs; + std::unordered_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { @@ -1373,15 +1374,15 @@ namespace Net { const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const int i_size = min((int)selected_iface.size(), 10); - const long long down_max = (net_auto ? graph_max.at("download") : ((long long)(Config::getI("net_download")) << 20) / 8); - const long long up_max = (net_auto ? graph_max.at("upload") : ((long long)(Config::getI("net_upload")) << 20) / 8); + const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); + const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs graphs.clear(); - if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty()) + if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty()) return out + Fx::reset; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", @@ -1395,7 +1396,7 @@ namespace Net { out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right - + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; @@ -1419,13 +1420,13 @@ namespace Net { //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { - out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + safeVal(graphs, dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); - const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); - const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); - const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); - const string total = floating_humanizer(net.stat.at(dir).total); + const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); + const string total = floating_humanizer(safeVal(net.stat, dir).total); const string symbol = (dir == "upload" ? "▲" : "▼"); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); cy += (b_height == 5 ? 2 : 1); @@ -1453,9 +1454,9 @@ namespace Proc { bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; - unordered_flat_map p_graphs; - unordered_flat_map p_wide_cmd; - unordered_flat_map p_counters; + std::unordered_map p_graphs; + std::unordered_map p_wide_cmd; + std::unordered_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; @@ -1794,7 +1795,7 @@ namespace Proc { p_counters.erase(p.pid); } else - p_counters.at(p.pid) = 0; + p_counters[p.pid] = 0; } out += Fx::reset; @@ -1890,7 +1891,7 @@ namespace Proc { + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") - + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + safeVal(p_graphs, p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; } @@ -1926,8 +1927,6 @@ namespace Proc { else ++element; } - p_graphs.compact(); - p_counters.compact(); for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { @@ -1936,7 +1935,6 @@ namespace Proc { else ++element; } - p_wide_cmd.compact(); } if (selected == 0 and selected_pid != 0) { diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index af57ed8..b049a57 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::deque; using std::string; @@ -108,7 +107,7 @@ namespace Draw { long long offset; long long last = 0, max_value = 0; bool current = true, tty_mode = false; - unordered_flat_map> graphs = { {true, {}}, {false, {}}}; + std::unordered_map> graphs = { {true, {}}, {false, {}}}; //* Create two representations of the graph to switch between to represent two values for each braille character void _create(const deque& data, int data_offset); @@ -135,6 +134,6 @@ namespace Draw { namespace Proc { extern Draw::TextEdit filter; - extern unordered_flat_map p_graphs; - extern unordered_flat_map p_counters; + extern std::unordered_map p_graphs; + extern std::unordered_map p_counters; } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 88f307b..fb9e46d 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -49,7 +49,7 @@ namespace rng = std::ranges; namespace Input { //* Map for translating key codes to readable values - const unordered_flat_map Key_escapes = { + const std::unordered_map Key_escapes = { {"\033", "escape"}, {"\n", "enter"}, {" ", "space"}, @@ -92,7 +92,7 @@ namespace Input { std::atomic interrupt (false); std::atomic polling (false); array mouse_pos; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; deque history(50, ""); string old_filter; diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..fc6fda0 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -44,7 +43,7 @@ namespace Input { }; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; extern atomic interrupt; extern atomic polling; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 206052f..bc97dc2 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -31,7 +31,6 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_shared.hpp" -using robin_hood::unordered_flat_map; using std::array; using std::ceil; using std::max; @@ -123,7 +122,7 @@ namespace Menu { #endif }; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -1165,7 +1164,7 @@ namespace Menu { static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; - static const unordered_flat_map>> optionsList = { + static const std::unordered_map>> optionsList = { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 0e8832f..74b30d1 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -38,7 +38,7 @@ namespace Menu { extern bool redraw; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; //* Creates a message box centered on screen //? Height of box is determined by size of content vector diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index fe0f334..b159a97 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -29,7 +29,7 @@ using namespace Tools; namespace Gpu { vector gpu_names; vector gpu_b_height_offsets; - unordered_flat_map> shared_gpu_percent = { + std::unordered_map> shared_gpu_percent = { {"gpu-average", {}}, {"gpu-vram-total", {}}, {"gpu-pwr-total", {}}, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 017c7f8..ebd1073 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,10 +26,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -98,7 +97,7 @@ namespace Gpu { extern vector gpu_b_height_offsets; extern long long gpu_pwr_total_max; - extern unordered_flat_map> shared_gpu_percent; // averages, power/vram total + extern std::unordered_map> shared_gpu_percent; // averages, power/vram total const array mem_names { "used"s, "free"s }; @@ -124,7 +123,7 @@ namespace Gpu { //* Per-device container for GPU info struct gpu_info { - unordered_flat_map> gpu_percent = { + std::unordered_map> gpu_percent = { {"gpu-totals", {}}, {"gpu-vram-totals", {}}, {"gpu-pwr-totals", {}}, @@ -181,7 +180,7 @@ namespace Cpu { extern tuple current_bat; struct cpu_info { - unordered_flat_map> cpu_percent = { + std::unordered_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -207,8 +206,8 @@ namespace Cpu { string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> unordered_flat_map; - extern unordered_flat_map core_mapping; + auto get_core_mapping() -> std::unordered_map; + extern std::unordered_map core_mapping; auto get_cpuHz() -> string; @@ -242,13 +241,13 @@ namespace Mem { }; struct mem_info { - unordered_flat_map stats = + std::unordered_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - unordered_flat_map> percent = + std::unordered_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; - unordered_flat_map disks; + std::unordered_map disks; vector disks_order; }; @@ -270,7 +269,7 @@ namespace Net { extern string selected_iface; extern vector interfaces; extern bool rescale; - extern unordered_flat_map graph_max; + extern std::unordered_map graph_max; struct net_stat { uint64_t speed{}; // defaults to 0 @@ -282,14 +281,14 @@ namespace Net { }; struct net_info { - unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; - unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; + std::unordered_map> bandwidth = { {"download", {}}, {"upload", {}} }; + std::unordered_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" bool connected{}; // defaults to false }; - extern unordered_flat_map current_net; + extern std::unordered_map current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -322,7 +321,7 @@ namespace Proc { }; //? Translation from process state char to explanative string - const unordered_flat_map proc_states = { + const std::unordered_map proc_states = { {'R', "Running"}, {'S', "Sleeping"}, {'D', "Waiting"}, diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 8a641fe..136adbf 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -42,11 +42,11 @@ namespace Theme { fs::path theme_dir; fs::path user_theme_dir; vector themes; - unordered_flat_map colors; - unordered_flat_map> rgbs; - unordered_flat_map> gradients; + std::unordered_map colors; + std::unordered_map> rgbs; + std::unordered_map> gradients; - const unordered_flat_map Default_theme = { + const std::unordered_map Default_theme = { { "main_bg", "#00" }, { "main_fg", "#cc" }, { "title", "#ee" }, @@ -91,7 +91,7 @@ namespace Theme { { "process_end", "#d45454" } }; - const unordered_flat_map TTY_theme = { + const std::unordered_map TTY_theme = { { "main_bg", "\x1b[0;40m" }, { "main_fg", "\x1b[37m" }, { "title", "\x1b[97m" }, @@ -224,7 +224,7 @@ namespace Theme { } //* Generate colors and rgb decimal vectors for the theme - void generateColors(const unordered_flat_map& source) { + void generateColors(const std::unordered_map& source) { vector t_rgb; string depth; bool t_to_256 = Config::getB("lowcolor"); @@ -372,7 +372,7 @@ namespace Theme { //* Load a .theme file from disk auto loadFile(const string& filename) { - unordered_flat_map theme_out; + std::unordered_map theme_out; const fs::path filepath = filename; if (not fs::exists(filepath)) return Default_theme; diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index 3fb30a4..cc3fbdf 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,12 +22,11 @@ tab-size = 4 #include #include #include -#include +#include using std::array; using std::string; using std::vector; -using robin_hood::unordered_flat_map; namespace Theme { extern std::filesystem::path theme_dir; @@ -54,9 +53,9 @@ namespace Theme { //* Set current theme from current "color_theme" value in config void setTheme(); - extern unordered_flat_map colors; - extern unordered_flat_map> rgbs; - extern unordered_flat_map> gradients; + extern std::unordered_map colors; + extern std::unordered_map> rgbs; + extern std::unordered_map> gradients; //* Return escape code for color inline const string& c(const string& name) { return colors.at(name); } diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 88bf711..aaa9e54 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -30,7 +30,7 @@ tab-size = 4 #include #include -#include "robin_hood.h" +#include "unordered_map" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" @@ -43,7 +43,6 @@ using std::flush; using std::max; using std::string_view; using std::to_string; -using robin_hood::unordered_flat_map; using namespace std::literals; // to use operator""s diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index c4d9670..c7b2cc3 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -31,6 +31,10 @@ tab-size = 4 #include #include #include +#include +#ifdef BTOP_DEBUG +#include +#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -146,6 +150,35 @@ namespace Term { void restore(); } +//* Simple logging implementation +namespace Logger { + const vector log_levels = { + "DISABLED", + "ERROR", + "WARNING", + "INFO", + "DEBUG", + }; + extern std::filesystem::path logfile; + + enum Level : size_t { + DISABLED = 0, + ERROR = 1, + WARNING = 2, + INFO = 3, + DEBUG = 4, + }; + + //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" + void set(const string& level); + + void log_write(const Level level, const string& msg); + inline void error(const string msg) { log_write(ERROR, msg); } + inline void warning(const string msg) { log_write(WARNING, msg); } + inline void info(const string msg) { log_write(INFO, msg); } + inline void debug(const string msg) { log_write(DEBUG, msg); } +} + //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Tools { @@ -304,6 +337,50 @@ namespace Tools { //* Add std::string operator * : Repeat string number of times std::string operator*(const string& str, int64_t n); + template +#ifdef BTOP_DEBUG + T safeVal(const std::unordered_map& map, const K& key, T fallback = T(), std::source_location loc = std::source_location::current()) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] in file: {} on line: {}", key, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + T safeVal(const std::unordered_map& map, const K& key, T fallback = T()) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] (Compile btop with DEBUG=true for more extensive logging!)", key)); + return fallback; + } + }; +#endif + + template +#ifdef BTOP_DEBUG + T safeVal(const std::vector& vec, const size_t& index, T fallback = T(), std::source_location loc = std::source_location::current()) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] in file: {} on line: {}", index, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + T safeVal(const std::vector& vec, const size_t& index, T fallback = T()) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] (Compile btop with DEBUG=true for more extensive logging!)", index)); + return fallback; + } + }; +#endif + + + //* Return current time in format string strf_time(const string& strf); @@ -342,35 +419,6 @@ namespace Tools { auto celsius_to(const long long& celsius, const string& scale) -> tuple; } -//* Simple logging implementation -namespace Logger { - const vector log_levels = { - "DISABLED", - "ERROR", - "WARNING", - "INFO", - "DEBUG", - }; - extern std::filesystem::path logfile; - - enum Level : size_t { - DISABLED = 0, - ERROR = 1, - WARNING = 2, - INFO = 3, - DEBUG = 4, - }; - - //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" - void set(const string& level); - - void log_write(const Level level, const string& msg); - inline void error(const string msg) { log_write(ERROR, msg); } - inline void warning(const string msg) { log_write(WARNING, msg); } - inline void info(const string msg) { log_write(INFO, msg); } - inline void debug(const string msg) { log_write(DEBUG, msg); } -} - namespace Tools { //* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running //* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 7f93322..670acdd 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -204,7 +204,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -323,8 +323,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -557,7 +557,7 @@ namespace Mem { } } - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { // this bit is for 'regular' mounts static struct statinfo cur; long double etime = 0; @@ -691,7 +691,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -807,13 +807,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -892,7 +892,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1037,7 +1037,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 33a0be8..1a8dbb4 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -17,7 +17,8 @@ tab-size = 4 */ #include -#include +#include +#include #include #include #include @@ -31,6 +32,8 @@ tab-size = 4 #include #include #include +#include +#include #if defined(RSMI_STATIC) #include @@ -94,10 +97,10 @@ namespace Cpu { int64_t crit{}; // defaults to 0 }; - unordered_flat_map found_sensors; + std::unordered_map found_sensors; string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } namespace Gpu { @@ -298,7 +301,7 @@ namespace Cpu { "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s }; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -595,8 +598,8 @@ namespace Cpu { return cpuhz; } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo @@ -669,7 +672,7 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0, ""}; static string auto_sel; - static unordered_flat_map batteries; + static std::unordered_map batteries; //? Get paths to needed files and check for valid values on first run if (batteries.empty() and has_battery) { @@ -1613,7 +1616,7 @@ namespace Mem { auto only_physical = Config::getB("only_physical"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; - static unordered_flat_map>> disks_stats_promises; + static std::unordered_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -2048,13 +2051,13 @@ namespace Mem { } namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors{}; // defaults to 0 - unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; - unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; + std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; uint64_t timestamp{}; // defaults to 0 @@ -2193,7 +2196,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -2263,7 +2265,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev{}; // defaults to false @@ -2278,7 +2280,7 @@ namespace Proc { detail_container detailed; constexpr size_t KTHREADD = 2; - static robin_hood::unordered_set kernels_procs = {KTHREADD}; + static std::unordered_set kernels_procs = {KTHREADD}; //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 681afc4..95ab322 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -194,7 +194,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -337,8 +337,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3); } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; natural_t cpu_count; @@ -599,7 +599,7 @@ namespace Mem { io_object_t &object; }; - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { io_registry_entry_t drive; io_iterator_t drive_list; @@ -716,7 +716,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -829,13 +829,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -912,7 +912,7 @@ namespace Net { } // else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1057,7 +1057,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; From ced47a960f649d3c4e81c91128fe97a81e4c4ad5 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 02:26:13 +0100 Subject: [PATCH 23/91] Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback --- Makefile | 8 +- src/btop.cpp | 3 +- src/btop_config.cpp | 13 +-- src/btop_config.hpp | 15 ++- src/btop_draw.cpp | 172 +++++++++++++++++------------------ src/btop_draw.hpp | 9 +- src/btop_input.cpp | 4 +- src/btop_input.hpp | 5 +- src/btop_menu.cpp | 7 +- src/btop_menu.hpp | 2 +- src/btop_shared.cpp | 2 +- src/btop_shared.hpp | 29 +++--- src/btop_theme.cpp | 14 +-- src/btop_theme.hpp | 9 +- src/btop_tools.cpp | 3 +- src/btop_tools.hpp | 106 +++++++++++++++------ src/freebsd/btop_collect.cpp | 22 ++--- src/linux/btop_collect.cpp | 30 +++--- src/osx/btop_collect.cpp | 22 ++--- 19 files changed, 261 insertions(+), 214 deletions(-) diff --git a/Makefile b/Makefile index 970e818..2b02f3d 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,10 @@ CLANG_WORKS = false GCC_WORKS = false MIN_CLANG_VERSION = 16 +ifeq ($(DEBUG),true) + override ADDFLAGS += -DBTOP_DEBUG +endif + #? Supported is Clang 16.0.0 and later ifeq ($(CXX_IS_CLANG),true) ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) @@ -279,13 +283,13 @@ directories: clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) - @cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true + @test -e lib/rocm_smi_lib/build && cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true #? Clean Objects and Binaries distclean: clean @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @rm -rf $(TARGETDIR) - @rm -rf lib/rocm_smi_lib/build + @test -e lib/rocm_smi_lib/build && rm -rf lib/rocm_smi_lib/build || true install: @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" diff --git a/src/btop.cpp b/src/btop.cpp index 252f2a9..8a0eb05 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -32,6 +32,7 @@ tab-size = 4 #include #include #include +#include #ifdef __APPLE__ #include #include @@ -416,7 +417,7 @@ namespace Runner { }; string debug_bg; - unordered_flat_map> debug_times; + std::unordered_map> debug_times; class MyNumPunct : public std::numpunct { diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 0f723b7..6ddfd43 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,6 +21,7 @@ tab-size = 4 #include #include #include +#include #include @@ -215,7 +216,7 @@ namespace Config { #endif }; - unordered_flat_map strings = { + std::unordered_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, @@ -251,9 +252,9 @@ namespace Config { {"show_gpu_info", "Auto"} #endif }; - unordered_flat_map stringsTmp; + std::unordered_map stringsTmp; - unordered_flat_map bools = { + std::unordered_map bools = { {"theme_background", true}, {"truecolor", true}, {"rounded_corners", true}, @@ -304,9 +305,9 @@ namespace Config { {"gpu_mirror_graph", true}, #endif }; - unordered_flat_map boolsTmp; + std::unordered_map boolsTmp; - unordered_flat_map ints = { + std::unordered_map ints = { {"update_ms", 2000}, {"net_download", 100}, {"net_upload", 100}, @@ -317,7 +318,7 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - unordered_flat_map intsTmp; + std::unordered_map intsTmp; bool _locked(const std::string_view name) { atomic_wait(writelock, true); diff --git a/src/btop_config.hpp b/src/btop_config.hpp index c71d29c..2b586af 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -22,11 +22,10 @@ tab-size = 4 #include #include -#include +#include using std::string; using std::vector; -using robin_hood::unordered_flat_map; //* Functions and variables for reading and writing the btop config file namespace Config { @@ -34,12 +33,12 @@ namespace Config { extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern unordered_flat_map strings; - extern unordered_flat_map stringsTmp; - extern unordered_flat_map bools; - extern unordered_flat_map boolsTmp; - extern unordered_flat_map ints; - extern unordered_flat_map intsTmp; + extern std::unordered_map strings; + extern std::unordered_map stringsTmp; + extern std::unordered_map bools; + extern std::unordered_map boolsTmp; + extern std::unordered_map ints; + extern std::unordered_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 5ae357e..6f790eb 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -22,6 +22,7 @@ tab-size = 4 #include #include #include +#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -54,7 +55,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const unordered_flat_map> graph_symbols = { + const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -300,7 +301,7 @@ namespace Draw { return false; } - static const unordered_flat_map clock_custom_format = { + static const std::unordered_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} @@ -560,12 +561,12 @@ namespace Cpu { const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; string out; out.reserve(width * height); @@ -608,7 +609,7 @@ namespace Cpu { if (gpu.supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpu.supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -617,12 +618,12 @@ namespace Cpu { bool utilization_support = gpu.supported_functions.gpu_utilization; if (++i < gpus.size()) { if (utilization_support) - graph = Draw::Graph{graph_width, graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true}; + graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true}; } else { if (utilization_support) graph = Draw::Graph{ graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, - graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true + graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true }; break; } @@ -630,7 +631,7 @@ namespace Cpu { } else { graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", Gpu::shared_gpu_percent.at(graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true }; gpu_temp_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); @@ -638,7 +639,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -649,7 +650,7 @@ namespace Cpu { #endif graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", cpu.cpu_percent.at(graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true }; #ifdef GPU_SUPPORT if (std::cmp_less(Gpu::shown, gpus.size())) { gpu_temp_graphs.resize(gpus.size()); @@ -659,7 +660,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -692,10 +693,10 @@ namespace Cpu { if (show_temps) { temp_graphs.clear(); - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23); } } } @@ -707,7 +708,7 @@ namespace Cpu { static long old_seconds{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; - static const unordered_flat_map bat_symbols = { + static const std::unordered_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, @@ -749,7 +750,7 @@ namespace Cpu { if (graph_field.starts_with("gpu")) if (graph_field.find("totals") != string::npos) for (unsigned long i = 0;;) { - out += graphs[i](gpus[i].gpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw)); if (gpus.size() > 1) { auto i_str = to_string(i); out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str @@ -761,13 +762,13 @@ namespace Cpu { else break; } else - out += graphs[0](Gpu::shared_gpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw)); else #else (void)graph_height; (void)graph_width; #endif - out += graphs[0](cpu.cpu_percent.at(graph_field), (data_same or redraw)); + out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); }; draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); @@ -792,14 +793,14 @@ namespace Cpu { out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) - + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) + + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { - const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1 or b_columns > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color - + temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); + + safeVal(temp_graphs, 0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; @@ -814,17 +815,17 @@ namespace Cpu { + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + + safeVal(core_graphs, n)(safeVal(cpu.core_percent, n), data_same or redraw); - out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); - out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; + out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); + out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { - const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) - + temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); + + safeVal(temp_graphs, n+1)(safeVal(cpu.temp, n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -882,14 +883,14 @@ namespace Cpu { } if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); if (gpus[i].supported_functions.gpu_utilization) { - string meter = gpu_meters[i](gpus[i].gpu_percent.at("gpu-totals").back()); + string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()); out += (meter.size() > 1 ? " " : "") + meter - + Theme::g("cpu").at(gpus[i].gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpus[i].gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; } else out += Mv::r(gpu_meter_width); if (gpus[i].supported_functions.mem_used) { - out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(gpus[i].gpu_percent.at("gpu-vram-totals").back()) - + gpu_mem_graphs[i](gpus[i].gpu_percent.at("gpu-vram-totals"), data_same or redraw) + Theme::c("main_fg") + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back()) + + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg") + rjust(floating_humanizer(gpus[i].mem_used, true), 5); if (gpus[i].supported_functions.mem_total) out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); @@ -967,12 +968,12 @@ namespace Gpu { out += box[index]; if (gpu.supported_functions.gpu_utilization) { - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", gpu.gpu_percent.at("gpu-totals"), graph_symbol, false, true}; // TODO cpu -> gpu + graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu if (not single_graph) { graph_lower = Draw::Graph{ x + width - b_width - 3, graph_low_height, "cpu", - gpu.gpu_percent.at("gpu-totals"), + safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, Config::getB("cpu_invert_lower"), true }; @@ -986,7 +987,7 @@ namespace Gpu { if (gpu.supported_functions.mem_utilization) mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol}; + mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; } @@ -994,12 +995,12 @@ namespace Gpu { //? Gpu graph, meter & clock speed if (gpu.supported_functions.gpu_utilization) { - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); if (not single_graph) - out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); + out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(gpu.gpu_percent.at("gpu-totals").back()) - + Theme::g("cpu").at(gpu.gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpu.gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back()) + + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; //? Temperature graph, I assume the device supports utilization if it supports temperature if (show_temps) { @@ -1019,10 +1020,10 @@ namespace Gpu { //? Power usage meter, power state if (gpu.supported_functions.pwr_usage) { - out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(gpu.gpu_percent.at("gpu-pwr-totals").back()) - + Theme::g("cached").at(gpu.gpu_percent.at("gpu-pwr-totals").back()) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; + out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back()) + + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card - out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(gpu.pwr_state) + to_string(gpu.pwr_state); + out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); } if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { @@ -1038,9 +1039,9 @@ namespace Gpu { + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right - + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(gpu.gpu_percent.at("gpu-vram-totals"), (data_same or redraw[index])) + + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index])) + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub - + Mv::r(3) + rjust(to_string(gpu.gpu_percent.at("gpu-vram-totals").back()), 3) + '%'; + + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; //? Memory utilization if (gpu.supported_functions.mem_utilization) @@ -1096,11 +1097,11 @@ namespace Mem { int disks_io_half = 0; bool shown = true, redraw = true; string box; - unordered_flat_map mem_meters; - unordered_flat_map mem_graphs; - unordered_flat_map disk_meters_used; - unordered_flat_map disk_meters_free; - unordered_flat_map io_graphs; + std::unordered_map mem_meters; + std::unordered_map mem_graphs; + std::unordered_map disk_meters_used; + std::unordered_map disk_meters_free; + std::unordered_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; @@ -1132,14 +1133,14 @@ namespace Mem { for (const auto& name : mem_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -1148,7 +1149,7 @@ namespace Mem { //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { - unordered_flat_map custom_speeds; + std::unordered_map custom_speeds; int half_height = 0; if (io_mode) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); @@ -1230,7 +1231,7 @@ namespace Mem { if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; cy += 1; } - out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8) + out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8) + Theme::c("main_fg") + Fx::ub; cy += 1; title = "Used"; @@ -1239,13 +1240,13 @@ namespace Mem { title = "Free"; if (title.empty()) title = capitalize(name); - const string humanized = floating_humanizer(mem.stats.at(name)); + const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); + const string graphics = (use_graphs ? safeVal(mem_graphs, name)(safeVal(mem.percent, name), redraw or data_same) : safeVal(mem_meters, name)(safeVal(mem.percent, name).back())); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) - + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); + + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4); cy += (graph_height == 0 ? 2 : graph_height + 1); } else { @@ -1268,7 +1269,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); if (disk.io_read.empty()) continue; const string total = floating_humanizer(disk.total, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) @@ -1278,14 +1279,14 @@ namespace Mem { out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) - + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) + (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW"); if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount)({comb_val}, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized; cy += disks_io_h; } @@ -1293,8 +1294,8 @@ namespace Mem { const string human_read = (disk.io_read.back() > 0 ? "▲" + floating_humanizer(disk.io_read.back(), true) : "R"); const string human_write = (disk.io_write.back() > 0 ? "▼" + floating_humanizer(disk.io_write.back(), true) : "W"); if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) - + Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) + + Mv::d(1) + safeVal(io_graphs, mount + "_write")(disk.io_write, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write; cy += disks_io_h; } @@ -1304,7 +1305,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); @@ -1319,18 +1320,18 @@ namespace Mem { if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) - + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' - + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); + + safeVal(disk_meters_used, mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' - + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); + + safeVal(disk_meters_free, mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } @@ -1353,7 +1354,7 @@ namespace Net { int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; string old_ip; - unordered_flat_map graphs; + std::unordered_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { @@ -1373,15 +1374,15 @@ namespace Net { const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const int i_size = min((int)selected_iface.size(), 10); - const long long down_max = (net_auto ? graph_max.at("download") : ((long long)(Config::getI("net_download")) << 20) / 8); - const long long up_max = (net_auto ? graph_max.at("upload") : ((long long)(Config::getI("net_upload")) << 20) / 8); + const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); + const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs graphs.clear(); - if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty()) + if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty()) return out + Fx::reset; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", @@ -1395,7 +1396,7 @@ namespace Net { out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right - + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; @@ -1419,13 +1420,13 @@ namespace Net { //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { - out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + safeVal(graphs, dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); - const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); - const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); - const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); - const string total = floating_humanizer(net.stat.at(dir).total); + const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); + const string total = floating_humanizer(safeVal(net.stat, dir).total); const string symbol = (dir == "upload" ? "▲" : "▼"); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); cy += (b_height == 5 ? 2 : 1); @@ -1453,9 +1454,9 @@ namespace Proc { bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; - unordered_flat_map p_graphs; - unordered_flat_map p_wide_cmd; - unordered_flat_map p_counters; + std::unordered_map p_graphs; + std::unordered_map p_wide_cmd; + std::unordered_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; @@ -1794,7 +1795,7 @@ namespace Proc { p_counters.erase(p.pid); } else - p_counters.at(p.pid) = 0; + p_counters[p.pid] = 0; } out += Fx::reset; @@ -1890,7 +1891,7 @@ namespace Proc { + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") - + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + safeVal(p_graphs, p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; } @@ -1926,8 +1927,6 @@ namespace Proc { else ++element; } - p_graphs.compact(); - p_counters.compact(); for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { @@ -1936,7 +1935,6 @@ namespace Proc { else ++element; } - p_wide_cmd.compact(); } if (selected == 0 and selected_pid != 0) { diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index af57ed8..b049a57 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::deque; using std::string; @@ -108,7 +107,7 @@ namespace Draw { long long offset; long long last = 0, max_value = 0; bool current = true, tty_mode = false; - unordered_flat_map> graphs = { {true, {}}, {false, {}}}; + std::unordered_map> graphs = { {true, {}}, {false, {}}}; //* Create two representations of the graph to switch between to represent two values for each braille character void _create(const deque& data, int data_offset); @@ -135,6 +134,6 @@ namespace Draw { namespace Proc { extern Draw::TextEdit filter; - extern unordered_flat_map p_graphs; - extern unordered_flat_map p_counters; + extern std::unordered_map p_graphs; + extern std::unordered_map p_counters; } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 88f307b..fb9e46d 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -49,7 +49,7 @@ namespace rng = std::ranges; namespace Input { //* Map for translating key codes to readable values - const unordered_flat_map Key_escapes = { + const std::unordered_map Key_escapes = { {"\033", "escape"}, {"\n", "enter"}, {" ", "space"}, @@ -92,7 +92,7 @@ namespace Input { std::atomic interrupt (false); std::atomic polling (false); array mouse_pos; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; deque history(50, ""); string old_filter; diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..fc6fda0 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -44,7 +43,7 @@ namespace Input { }; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; extern atomic interrupt; extern atomic polling; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 206052f..bc97dc2 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -31,7 +31,6 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_shared.hpp" -using robin_hood::unordered_flat_map; using std::array; using std::ceil; using std::max; @@ -123,7 +122,7 @@ namespace Menu { #endif }; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -1165,7 +1164,7 @@ namespace Menu { static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; - static const unordered_flat_map>> optionsList = { + static const std::unordered_map>> optionsList = { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 0e8832f..74b30d1 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -38,7 +38,7 @@ namespace Menu { extern bool redraw; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; //* Creates a message box centered on screen //? Height of box is determined by size of content vector diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index fe0f334..b159a97 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -29,7 +29,7 @@ using namespace Tools; namespace Gpu { vector gpu_names; vector gpu_b_height_offsets; - unordered_flat_map> shared_gpu_percent = { + std::unordered_map> shared_gpu_percent = { {"gpu-average", {}}, {"gpu-vram-total", {}}, {"gpu-pwr-total", {}}, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 017c7f8..ebd1073 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,10 +26,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -98,7 +97,7 @@ namespace Gpu { extern vector gpu_b_height_offsets; extern long long gpu_pwr_total_max; - extern unordered_flat_map> shared_gpu_percent; // averages, power/vram total + extern std::unordered_map> shared_gpu_percent; // averages, power/vram total const array mem_names { "used"s, "free"s }; @@ -124,7 +123,7 @@ namespace Gpu { //* Per-device container for GPU info struct gpu_info { - unordered_flat_map> gpu_percent = { + std::unordered_map> gpu_percent = { {"gpu-totals", {}}, {"gpu-vram-totals", {}}, {"gpu-pwr-totals", {}}, @@ -181,7 +180,7 @@ namespace Cpu { extern tuple current_bat; struct cpu_info { - unordered_flat_map> cpu_percent = { + std::unordered_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -207,8 +206,8 @@ namespace Cpu { string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> unordered_flat_map; - extern unordered_flat_map core_mapping; + auto get_core_mapping() -> std::unordered_map; + extern std::unordered_map core_mapping; auto get_cpuHz() -> string; @@ -242,13 +241,13 @@ namespace Mem { }; struct mem_info { - unordered_flat_map stats = + std::unordered_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - unordered_flat_map> percent = + std::unordered_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; - unordered_flat_map disks; + std::unordered_map disks; vector disks_order; }; @@ -270,7 +269,7 @@ namespace Net { extern string selected_iface; extern vector interfaces; extern bool rescale; - extern unordered_flat_map graph_max; + extern std::unordered_map graph_max; struct net_stat { uint64_t speed{}; // defaults to 0 @@ -282,14 +281,14 @@ namespace Net { }; struct net_info { - unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; - unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; + std::unordered_map> bandwidth = { {"download", {}}, {"upload", {}} }; + std::unordered_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" bool connected{}; // defaults to false }; - extern unordered_flat_map current_net; + extern std::unordered_map current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -322,7 +321,7 @@ namespace Proc { }; //? Translation from process state char to explanative string - const unordered_flat_map proc_states = { + const std::unordered_map proc_states = { {'R', "Running"}, {'S', "Sleeping"}, {'D', "Waiting"}, diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 8a641fe..136adbf 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -42,11 +42,11 @@ namespace Theme { fs::path theme_dir; fs::path user_theme_dir; vector themes; - unordered_flat_map colors; - unordered_flat_map> rgbs; - unordered_flat_map> gradients; + std::unordered_map colors; + std::unordered_map> rgbs; + std::unordered_map> gradients; - const unordered_flat_map Default_theme = { + const std::unordered_map Default_theme = { { "main_bg", "#00" }, { "main_fg", "#cc" }, { "title", "#ee" }, @@ -91,7 +91,7 @@ namespace Theme { { "process_end", "#d45454" } }; - const unordered_flat_map TTY_theme = { + const std::unordered_map TTY_theme = { { "main_bg", "\x1b[0;40m" }, { "main_fg", "\x1b[37m" }, { "title", "\x1b[97m" }, @@ -224,7 +224,7 @@ namespace Theme { } //* Generate colors and rgb decimal vectors for the theme - void generateColors(const unordered_flat_map& source) { + void generateColors(const std::unordered_map& source) { vector t_rgb; string depth; bool t_to_256 = Config::getB("lowcolor"); @@ -372,7 +372,7 @@ namespace Theme { //* Load a .theme file from disk auto loadFile(const string& filename) { - unordered_flat_map theme_out; + std::unordered_map theme_out; const fs::path filepath = filename; if (not fs::exists(filepath)) return Default_theme; diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index 3fb30a4..cc3fbdf 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,12 +22,11 @@ tab-size = 4 #include #include #include -#include +#include using std::array; using std::string; using std::vector; -using robin_hood::unordered_flat_map; namespace Theme { extern std::filesystem::path theme_dir; @@ -54,9 +53,9 @@ namespace Theme { //* Set current theme from current "color_theme" value in config void setTheme(); - extern unordered_flat_map colors; - extern unordered_flat_map> rgbs; - extern unordered_flat_map> gradients; + extern std::unordered_map colors; + extern std::unordered_map> rgbs; + extern std::unordered_map> gradients; //* Return escape code for color inline const string& c(const string& name) { return colors.at(name); } diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 88bf711..aaa9e54 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -30,7 +30,7 @@ tab-size = 4 #include #include -#include "robin_hood.h" +#include "unordered_map" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" @@ -43,7 +43,6 @@ using std::flush; using std::max; using std::string_view; using std::to_string; -using robin_hood::unordered_flat_map; using namespace std::literals; // to use operator""s diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index c4d9670..c7b2cc3 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -31,6 +31,10 @@ tab-size = 4 #include #include #include +#include +#ifdef BTOP_DEBUG +#include +#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -146,6 +150,35 @@ namespace Term { void restore(); } +//* Simple logging implementation +namespace Logger { + const vector log_levels = { + "DISABLED", + "ERROR", + "WARNING", + "INFO", + "DEBUG", + }; + extern std::filesystem::path logfile; + + enum Level : size_t { + DISABLED = 0, + ERROR = 1, + WARNING = 2, + INFO = 3, + DEBUG = 4, + }; + + //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" + void set(const string& level); + + void log_write(const Level level, const string& msg); + inline void error(const string msg) { log_write(ERROR, msg); } + inline void warning(const string msg) { log_write(WARNING, msg); } + inline void info(const string msg) { log_write(INFO, msg); } + inline void debug(const string msg) { log_write(DEBUG, msg); } +} + //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Tools { @@ -304,6 +337,50 @@ namespace Tools { //* Add std::string operator * : Repeat string number of times std::string operator*(const string& str, int64_t n); + template +#ifdef BTOP_DEBUG + T safeVal(const std::unordered_map& map, const K& key, T fallback = T(), std::source_location loc = std::source_location::current()) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] in file: {} on line: {}", key, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + T safeVal(const std::unordered_map& map, const K& key, T fallback = T()) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] (Compile btop with DEBUG=true for more extensive logging!)", key)); + return fallback; + } + }; +#endif + + template +#ifdef BTOP_DEBUG + T safeVal(const std::vector& vec, const size_t& index, T fallback = T(), std::source_location loc = std::source_location::current()) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] in file: {} on line: {}", index, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + T safeVal(const std::vector& vec, const size_t& index, T fallback = T()) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] (Compile btop with DEBUG=true for more extensive logging!)", index)); + return fallback; + } + }; +#endif + + + //* Return current time in format string strf_time(const string& strf); @@ -342,35 +419,6 @@ namespace Tools { auto celsius_to(const long long& celsius, const string& scale) -> tuple; } -//* Simple logging implementation -namespace Logger { - const vector log_levels = { - "DISABLED", - "ERROR", - "WARNING", - "INFO", - "DEBUG", - }; - extern std::filesystem::path logfile; - - enum Level : size_t { - DISABLED = 0, - ERROR = 1, - WARNING = 2, - INFO = 3, - DEBUG = 4, - }; - - //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" - void set(const string& level); - - void log_write(const Level level, const string& msg); - inline void error(const string msg) { log_write(ERROR, msg); } - inline void warning(const string msg) { log_write(WARNING, msg); } - inline void info(const string msg) { log_write(INFO, msg); } - inline void debug(const string msg) { log_write(DEBUG, msg); } -} - namespace Tools { //* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running //* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 7f93322..670acdd 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -204,7 +204,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -323,8 +323,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -557,7 +557,7 @@ namespace Mem { } } - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { // this bit is for 'regular' mounts static struct statinfo cur; long double etime = 0; @@ -691,7 +691,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -807,13 +807,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -892,7 +892,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1037,7 +1037,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 33a0be8..1a8dbb4 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -17,7 +17,8 @@ tab-size = 4 */ #include -#include +#include +#include #include #include #include @@ -31,6 +32,8 @@ tab-size = 4 #include #include #include +#include +#include #if defined(RSMI_STATIC) #include @@ -94,10 +97,10 @@ namespace Cpu { int64_t crit{}; // defaults to 0 }; - unordered_flat_map found_sensors; + std::unordered_map found_sensors; string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } namespace Gpu { @@ -298,7 +301,7 @@ namespace Cpu { "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s }; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -595,8 +598,8 @@ namespace Cpu { return cpuhz; } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo @@ -669,7 +672,7 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0, ""}; static string auto_sel; - static unordered_flat_map batteries; + static std::unordered_map batteries; //? Get paths to needed files and check for valid values on first run if (batteries.empty() and has_battery) { @@ -1613,7 +1616,7 @@ namespace Mem { auto only_physical = Config::getB("only_physical"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; - static unordered_flat_map>> disks_stats_promises; + static std::unordered_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -2048,13 +2051,13 @@ namespace Mem { } namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors{}; // defaults to 0 - unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; - unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; + std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; uint64_t timestamp{}; // defaults to 0 @@ -2193,7 +2196,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -2263,7 +2265,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev{}; // defaults to false @@ -2278,7 +2280,7 @@ namespace Proc { detail_container detailed; constexpr size_t KTHREADD = 2; - static robin_hood::unordered_set kernels_procs = {KTHREADD}; + static std::unordered_set kernels_procs = {KTHREADD}; //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 681afc4..95ab322 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -194,7 +194,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -337,8 +337,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3); } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; natural_t cpu_count; @@ -599,7 +599,7 @@ namespace Mem { io_object_t &object; }; - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { io_registry_entry_t drive; io_iterator_t drive_list; @@ -716,7 +716,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -829,13 +829,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -912,7 +912,7 @@ namespace Net { } // else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1057,7 +1057,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; From e15e0b71881641ea6d6751e3acd46631d91b494b Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 02:27:38 +0100 Subject: [PATCH 24/91] Revert "Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback" This reverts commit 6c87ab61969f028e49ad596139cff53eb6c3becc. --- Makefile | 8 +- src/btop.cpp | 3 +- src/btop_config.cpp | 13 ++- src/btop_config.hpp | 15 +-- src/btop_draw.cpp | 172 ++++++++++++++++++----------------- src/btop_draw.hpp | 9 +- src/btop_input.cpp | 4 +- src/btop_input.hpp | 5 +- src/btop_menu.cpp | 7 +- src/btop_menu.hpp | 2 +- src/btop_shared.cpp | 2 +- src/btop_shared.hpp | 29 +++--- src/btop_theme.cpp | 14 +-- src/btop_theme.hpp | 9 +- src/btop_tools.cpp | 3 +- src/btop_tools.hpp | 106 ++++++--------------- src/freebsd/btop_collect.cpp | 22 ++--- src/linux/btop_collect.cpp | 30 +++--- src/osx/btop_collect.cpp | 22 ++--- 19 files changed, 214 insertions(+), 261 deletions(-) diff --git a/Makefile b/Makefile index 2b02f3d..970e818 100644 --- a/Makefile +++ b/Makefile @@ -61,10 +61,6 @@ CLANG_WORKS = false GCC_WORKS = false MIN_CLANG_VERSION = 16 -ifeq ($(DEBUG),true) - override ADDFLAGS += -DBTOP_DEBUG -endif - #? Supported is Clang 16.0.0 and later ifeq ($(CXX_IS_CLANG),true) ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) @@ -283,13 +279,13 @@ directories: clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) - @test -e lib/rocm_smi_lib/build && cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true + @cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true #? Clean Objects and Binaries distclean: clean @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @rm -rf $(TARGETDIR) - @test -e lib/rocm_smi_lib/build && rm -rf lib/rocm_smi_lib/build || true + @rm -rf lib/rocm_smi_lib/build install: @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" diff --git a/src/btop.cpp b/src/btop.cpp index 8a0eb05..252f2a9 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -32,7 +32,6 @@ tab-size = 4 #include #include #include -#include #ifdef __APPLE__ #include #include @@ -417,7 +416,7 @@ namespace Runner { }; string debug_bg; - std::unordered_map> debug_times; + unordered_flat_map> debug_times; class MyNumPunct : public std::numpunct { diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6ddfd43..0f723b7 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,7 +21,6 @@ tab-size = 4 #include #include #include -#include #include @@ -216,7 +215,7 @@ namespace Config { #endif }; - std::unordered_map strings = { + unordered_flat_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, @@ -252,9 +251,9 @@ namespace Config { {"show_gpu_info", "Auto"} #endif }; - std::unordered_map stringsTmp; + unordered_flat_map stringsTmp; - std::unordered_map bools = { + unordered_flat_map bools = { {"theme_background", true}, {"truecolor", true}, {"rounded_corners", true}, @@ -305,9 +304,9 @@ namespace Config { {"gpu_mirror_graph", true}, #endif }; - std::unordered_map boolsTmp; + unordered_flat_map boolsTmp; - std::unordered_map ints = { + unordered_flat_map ints = { {"update_ms", 2000}, {"net_download", 100}, {"net_upload", 100}, @@ -318,7 +317,7 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - std::unordered_map intsTmp; + unordered_flat_map intsTmp; bool _locked(const std::string_view name) { atomic_wait(writelock, true); diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 2b586af..c71d29c 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -22,10 +22,11 @@ tab-size = 4 #include #include -#include +#include using std::string; using std::vector; +using robin_hood::unordered_flat_map; //* Functions and variables for reading and writing the btop config file namespace Config { @@ -33,12 +34,12 @@ namespace Config { extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern std::unordered_map strings; - extern std::unordered_map stringsTmp; - extern std::unordered_map bools; - extern std::unordered_map boolsTmp; - extern std::unordered_map ints; - extern std::unordered_map intsTmp; + extern unordered_flat_map strings; + extern unordered_flat_map stringsTmp; + extern unordered_flat_map bools; + extern unordered_flat_map boolsTmp; + extern unordered_flat_map ints; + extern unordered_flat_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 6f790eb..5ae357e 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -22,7 +22,6 @@ tab-size = 4 #include #include #include -#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -55,7 +54,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const std::unordered_map> graph_symbols = { + const unordered_flat_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -301,7 +300,7 @@ namespace Draw { return false; } - static const std::unordered_map clock_custom_format = { + static const unordered_flat_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} @@ -561,12 +560,12 @@ namespace Cpu { const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; - if (safeVal(cpu.cpu_percent, "total"s).empty() - or safeVal(cpu.core_percent, 0).empty() - or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; - if (safeVal(cpu.cpu_percent, "total"s).empty() - or safeVal(cpu.core_percent, 0).empty() - or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; + if (cpu.cpu_percent.at("total").empty() + or cpu.core_percent.at(0).empty() + or (show_temps and cpu.temp.at(0).empty())) return ""; + if (cpu.cpu_percent.at("total").empty() + or cpu.core_percent.at(0).empty() + or (show_temps and cpu.temp.at(0).empty())) return ""; string out; out.reserve(width * height); @@ -609,7 +608,7 @@ namespace Cpu { if (gpu.supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol }; if (gpu.supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -618,12 +617,12 @@ namespace Cpu { bool utilization_support = gpu.supported_functions.gpu_utilization; if (++i < gpus.size()) { if (utilization_support) - graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true}; + graph = Draw::Graph{graph_width, graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true}; } else { if (utilization_support) graph = Draw::Graph{ graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, - graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true + graph_height, "cpu", gpu.gpu_percent.at(graph_field), graph_symbol, invert, true }; break; } @@ -631,7 +630,7 @@ namespace Cpu { } else { graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", Gpu::shared_gpu_percent.at(graph_field), graph_symbol, invert, true }; gpu_temp_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); @@ -639,7 +638,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -650,7 +649,7 @@ namespace Cpu { #endif graphs.resize(1); graph_width = graph_default_width; - graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true }; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", cpu.cpu_percent.at(graph_field), graph_symbol, invert, true }; #ifdef GPU_SUPPORT if (std::cmp_less(Gpu::shown, gpus.size())) { gpu_temp_graphs.resize(gpus.size()); @@ -660,7 +659,7 @@ namespace Cpu { if (gpus[i].supported_functions.temp_info) gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) - gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", gpus[i].gpu_percent.at("gpu-vram-totals"), graph_symbol }; if (gpus[i].supported_functions.gpu_utilization) { gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; @@ -693,10 +692,10 @@ namespace Cpu { if (show_temps) { temp_graphs.clear(); - temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { - temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); } } } @@ -708,7 +707,7 @@ namespace Cpu { static long old_seconds{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; - static const std::unordered_map bat_symbols = { + static const unordered_flat_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, @@ -750,7 +749,7 @@ namespace Cpu { if (graph_field.starts_with("gpu")) if (graph_field.find("totals") != string::npos) for (unsigned long i = 0;;) { - out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw)); + out += graphs[i](gpus[i].gpu_percent.at(graph_field), (data_same or redraw)); if (gpus.size() > 1) { auto i_str = to_string(i); out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str @@ -762,13 +761,13 @@ namespace Cpu { else break; } else - out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw)); + out += graphs[0](Gpu::shared_gpu_percent.at(graph_field), (data_same or redraw)); else #else (void)graph_height; (void)graph_width; #endif - out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); + out += graphs[0](cpu.cpu_percent.at(graph_field), (data_same or redraw)); }; draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); @@ -793,14 +792,14 @@ namespace Cpu { out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) - + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) + + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { - const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1 or b_columns > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color - + safeVal(temp_graphs, 0)(safeVal(cpu.temp, 0), data_same or redraw); + + temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; @@ -815,17 +814,17 @@ namespace Cpu { + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + safeVal(core_graphs, n)(safeVal(cpu.core_percent, n), data_same or redraw); + + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); - out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); - out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; + out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); + out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { - const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); + const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) - + safeVal(temp_graphs, n+1)(safeVal(cpu.temp, n+1), data_same or redraw); + + temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -883,14 +882,14 @@ namespace Cpu { } if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); if (gpus[i].supported_functions.gpu_utilization) { - string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()); + string meter = gpu_meters[i](gpus[i].gpu_percent.at("gpu-totals").back()); out += (meter.size() > 1 ? " " : "") + meter - + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; + + Theme::g("cpu").at(gpus[i].gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpus[i].gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; } else out += Mv::r(gpu_meter_width); if (gpus[i].supported_functions.mem_used) { - out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back()) - + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg") + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(gpus[i].gpu_percent.at("gpu-vram-totals").back()) + + gpu_mem_graphs[i](gpus[i].gpu_percent.at("gpu-vram-totals"), data_same or redraw) + Theme::c("main_fg") + rjust(floating_humanizer(gpus[i].mem_used, true), 5); if (gpus[i].supported_functions.mem_total) out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); @@ -968,12 +967,12 @@ namespace Gpu { out += box[index]; if (gpu.supported_functions.gpu_utilization) { - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu + graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", gpu.gpu_percent.at("gpu-totals"), graph_symbol, false, true}; // TODO cpu -> gpu if (not single_graph) { graph_lower = Draw::Graph{ x + width - b_width - 3, graph_low_height, "cpu", - safeVal(gpu.gpu_percent, "gpu-totals"s), + gpu.gpu_percent.at("gpu-totals"), graph_symbol, Config::getB("cpu_invert_lower"), true }; @@ -987,7 +986,7 @@ namespace Gpu { if (gpu.supported_functions.mem_utilization) mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) - mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; + mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", gpu.gpu_percent.at("gpu-vram-totals"), graph_symbol}; } @@ -995,12 +994,12 @@ namespace Gpu { //? Gpu graph, meter & clock speed if (gpu.supported_functions.gpu_utilization) { - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); if (not single_graph) - out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); + out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(gpu.gpu_percent.at("gpu-totals"), (data_same or redraw[index])); - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back()) - + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(gpu.gpu_percent.at("gpu-totals").back()) + + Theme::g("cpu").at(gpu.gpu_percent.at("gpu-totals").back()) + rjust(to_string(gpu.gpu_percent.at("gpu-totals").back()), 4) + Theme::c("main_fg") + '%'; //? Temperature graph, I assume the device supports utilization if it supports temperature if (show_temps) { @@ -1020,10 +1019,10 @@ namespace Gpu { //? Power usage meter, power state if (gpu.supported_functions.pwr_usage) { - out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back()) - + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; + out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(gpu.gpu_percent.at("gpu-pwr-totals").back()) + + Theme::g("cached").at(gpu.gpu_percent.at("gpu-pwr-totals").back()) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card - out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); + out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(gpu.pwr_state) + to_string(gpu.pwr_state); } if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { @@ -1039,9 +1038,9 @@ namespace Gpu { + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right - + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index])) + + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(gpu.gpu_percent.at("gpu-vram-totals"), (data_same or redraw[index])) + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub - + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; + + Mv::r(3) + rjust(to_string(gpu.gpu_percent.at("gpu-vram-totals").back()), 3) + '%'; //? Memory utilization if (gpu.supported_functions.mem_utilization) @@ -1097,11 +1096,11 @@ namespace Mem { int disks_io_half = 0; bool shown = true, redraw = true; string box; - std::unordered_map mem_meters; - std::unordered_map mem_graphs; - std::unordered_map disk_meters_used; - std::unordered_map disk_meters_free; - std::unordered_map io_graphs; + unordered_flat_map mem_meters; + unordered_flat_map mem_graphs; + unordered_flat_map disk_meters_used; + unordered_flat_map disk_meters_free; + unordered_flat_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; @@ -1133,14 +1132,14 @@ namespace Mem { for (const auto& name : mem_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -1149,7 +1148,7 @@ namespace Mem { //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { - std::unordered_map custom_speeds; + unordered_flat_map custom_speeds; int half_height = 0; if (io_mode) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); @@ -1231,7 +1230,7 @@ namespace Mem { if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; cy += 1; } - out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8) + out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8) + Theme::c("main_fg") + Fx::ub; cy += 1; title = "Used"; @@ -1240,13 +1239,13 @@ namespace Mem { title = "Free"; if (title.empty()) title = capitalize(name); - const string humanized = floating_humanizer(safeVal(mem.stats, name)); + const string humanized = floating_humanizer(mem.stats.at(name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? safeVal(mem_graphs, name)(safeVal(mem.percent, name), redraw or data_same) : safeVal(mem_meters, name)(safeVal(mem.percent, name).back())); + const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) - + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4); + + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); cy += (graph_height == 0 ? 2 : graph_height + 1); } else { @@ -1269,7 +1268,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = safeVal(disks, mount); + const auto& disk = disks.at(mount); if (disk.io_read.empty()) continue; const string total = floating_humanizer(disk.total, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) @@ -1279,14 +1278,14 @@ namespace Mem { out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) - + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) + (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW"); if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount)({comb_val}, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized; cy += disks_io_h; } @@ -1294,8 +1293,8 @@ namespace Mem { const string human_read = (disk.io_read.back() > 0 ? "▲" + floating_humanizer(disk.io_read.back(), true) : "R"); const string human_write = (disk.io_write.back() > 0 ? "▼" + floating_humanizer(disk.io_write.back(), true) : "W"); if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) - + Mv::d(1) + safeVal(io_graphs, mount + "_write")(disk.io_write, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) + + Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write; cy += disks_io_h; } @@ -1305,7 +1304,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = safeVal(disks, mount); + const auto& disk = disks.at(mount); auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); @@ -1320,18 +1319,18 @@ namespace Mem { if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) - + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' - + safeVal(disk_meters_used, mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); + + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' - + safeVal(disk_meters_free, mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); + + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } @@ -1354,7 +1353,7 @@ namespace Net { int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; string old_ip; - std::unordered_map graphs; + unordered_flat_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { @@ -1374,15 +1373,15 @@ namespace Net { const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const int i_size = min((int)selected_iface.size(), 10); - const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); - const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); + const long long down_max = (net_auto ? graph_max.at("download") : ((long long)(Config::getI("net_download")) << 20) / 8); + const long long up_max = (net_auto ? graph_max.at("upload") : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs graphs.clear(); - if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty()) + if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty()) return out + Fx::reset; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", @@ -1396,7 +1395,7 @@ namespace Net { out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right - + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; @@ -1420,13 +1419,13 @@ namespace Net { //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { - out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + safeVal(graphs, dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); - const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); - const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); - const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); - const string total = floating_humanizer(safeVal(net.stat, dir).total); + const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); + const string total = floating_humanizer(net.stat.at(dir).total); const string symbol = (dir == "upload" ? "▲" : "▼"); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); cy += (b_height == 5 ? 2 : 1); @@ -1454,9 +1453,9 @@ namespace Proc { bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; - std::unordered_map p_graphs; - std::unordered_map p_wide_cmd; - std::unordered_map p_counters; + unordered_flat_map p_graphs; + unordered_flat_map p_wide_cmd; + unordered_flat_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; @@ -1795,7 +1794,7 @@ namespace Proc { p_counters.erase(p.pid); } else - p_counters[p.pid] = 0; + p_counters.at(p.pid) = 0; } out += Fx::reset; @@ -1891,7 +1890,7 @@ namespace Proc { + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") - + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + safeVal(p_graphs, p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; } @@ -1927,6 +1926,8 @@ namespace Proc { else ++element; } + p_graphs.compact(); + p_counters.compact(); for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { @@ -1935,6 +1936,7 @@ namespace Proc { else ++element; } + p_wide_cmd.compact(); } if (selected == 0 and selected_pid != 0) { diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index b049a57..af57ed8 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -21,9 +21,10 @@ tab-size = 4 #include #include #include -#include +#include #include +using robin_hood::unordered_flat_map; using std::array; using std::deque; using std::string; @@ -107,7 +108,7 @@ namespace Draw { long long offset; long long last = 0, max_value = 0; bool current = true, tty_mode = false; - std::unordered_map> graphs = { {true, {}}, {false, {}}}; + unordered_flat_map> graphs = { {true, {}}, {false, {}}}; //* Create two representations of the graph to switch between to represent two values for each braille character void _create(const deque& data, int data_offset); @@ -134,6 +135,6 @@ namespace Draw { namespace Proc { extern Draw::TextEdit filter; - extern std::unordered_map p_graphs; - extern std::unordered_map p_counters; + extern unordered_flat_map p_graphs; + extern unordered_flat_map p_counters; } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index fb9e46d..88f307b 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -49,7 +49,7 @@ namespace rng = std::ranges; namespace Input { //* Map for translating key codes to readable values - const std::unordered_map Key_escapes = { + const unordered_flat_map Key_escapes = { {"\033", "escape"}, {"\n", "enter"}, {" ", "space"}, @@ -92,7 +92,7 @@ namespace Input { std::atomic interrupt (false); std::atomic polling (false); array mouse_pos; - std::unordered_map mouse_mappings; + unordered_flat_map mouse_mappings; deque history(50, ""); string old_filter; diff --git a/src/btop_input.hpp b/src/btop_input.hpp index fc6fda0..5a1a28e 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -21,9 +21,10 @@ tab-size = 4 #include #include #include -#include +#include #include +using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -43,7 +44,7 @@ namespace Input { }; //? line, col, height, width - extern std::unordered_map mouse_mappings; + extern unordered_flat_map mouse_mappings; extern atomic interrupt; extern atomic polling; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index bc97dc2..206052f 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -31,6 +31,7 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_shared.hpp" +using robin_hood::unordered_flat_map; using std::array; using std::ceil; using std::max; @@ -122,7 +123,7 @@ namespace Menu { #endif }; - std::unordered_map mouse_mappings; + unordered_flat_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -1164,7 +1165,7 @@ namespace Menu { static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; - static const std::unordered_map>> optionsList = { + static const unordered_flat_map>> optionsList = { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 74b30d1..0e8832f 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -38,7 +38,7 @@ namespace Menu { extern bool redraw; //? line, col, height, width - extern std::unordered_map mouse_mappings; + extern unordered_flat_map mouse_mappings; //* Creates a message box centered on screen //? Height of box is determined by size of content vector diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index b159a97..fe0f334 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -29,7 +29,7 @@ using namespace Tools; namespace Gpu { vector gpu_names; vector gpu_b_height_offsets; - std::unordered_map> shared_gpu_percent = { + unordered_flat_map> shared_gpu_percent = { {"gpu-average", {}}, {"gpu-vram-total", {}}, {"gpu-pwr-total", {}}, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index ebd1073..017c7f8 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,9 +26,10 @@ tab-size = 4 #include #include #include -#include +#include #include +using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -97,7 +98,7 @@ namespace Gpu { extern vector gpu_b_height_offsets; extern long long gpu_pwr_total_max; - extern std::unordered_map> shared_gpu_percent; // averages, power/vram total + extern unordered_flat_map> shared_gpu_percent; // averages, power/vram total const array mem_names { "used"s, "free"s }; @@ -123,7 +124,7 @@ namespace Gpu { //* Per-device container for GPU info struct gpu_info { - std::unordered_map> gpu_percent = { + unordered_flat_map> gpu_percent = { {"gpu-totals", {}}, {"gpu-vram-totals", {}}, {"gpu-pwr-totals", {}}, @@ -180,7 +181,7 @@ namespace Cpu { extern tuple current_bat; struct cpu_info { - std::unordered_map> cpu_percent = { + unordered_flat_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -206,8 +207,8 @@ namespace Cpu { string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> std::unordered_map; - extern std::unordered_map core_mapping; + auto get_core_mapping() -> unordered_flat_map; + extern unordered_flat_map core_mapping; auto get_cpuHz() -> string; @@ -241,13 +242,13 @@ namespace Mem { }; struct mem_info { - std::unordered_map stats = + unordered_flat_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - std::unordered_map> percent = + unordered_flat_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; - std::unordered_map disks; + unordered_flat_map disks; vector disks_order; }; @@ -269,7 +270,7 @@ namespace Net { extern string selected_iface; extern vector interfaces; extern bool rescale; - extern std::unordered_map graph_max; + extern unordered_flat_map graph_max; struct net_stat { uint64_t speed{}; // defaults to 0 @@ -281,14 +282,14 @@ namespace Net { }; struct net_info { - std::unordered_map> bandwidth = { {"download", {}}, {"upload", {}} }; - std::unordered_map stat = { {"download", {}}, {"upload", {}} }; + unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; + unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" bool connected{}; // defaults to false }; - extern std::unordered_map current_net; + extern unordered_flat_map current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -321,7 +322,7 @@ namespace Proc { }; //? Translation from process state char to explanative string - const std::unordered_map proc_states = { + const unordered_flat_map proc_states = { {'R', "Running"}, {'S', "Sleeping"}, {'D', "Waiting"}, diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 136adbf..8a641fe 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -42,11 +42,11 @@ namespace Theme { fs::path theme_dir; fs::path user_theme_dir; vector themes; - std::unordered_map colors; - std::unordered_map> rgbs; - std::unordered_map> gradients; + unordered_flat_map colors; + unordered_flat_map> rgbs; + unordered_flat_map> gradients; - const std::unordered_map Default_theme = { + const unordered_flat_map Default_theme = { { "main_bg", "#00" }, { "main_fg", "#cc" }, { "title", "#ee" }, @@ -91,7 +91,7 @@ namespace Theme { { "process_end", "#d45454" } }; - const std::unordered_map TTY_theme = { + const unordered_flat_map TTY_theme = { { "main_bg", "\x1b[0;40m" }, { "main_fg", "\x1b[37m" }, { "title", "\x1b[97m" }, @@ -224,7 +224,7 @@ namespace Theme { } //* Generate colors and rgb decimal vectors for the theme - void generateColors(const std::unordered_map& source) { + void generateColors(const unordered_flat_map& source) { vector t_rgb; string depth; bool t_to_256 = Config::getB("lowcolor"); @@ -372,7 +372,7 @@ namespace Theme { //* Load a .theme file from disk auto loadFile(const string& filename) { - std::unordered_map theme_out; + unordered_flat_map theme_out; const fs::path filepath = filename; if (not fs::exists(filepath)) return Default_theme; diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index cc3fbdf..3fb30a4 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,11 +22,12 @@ tab-size = 4 #include #include #include -#include +#include using std::array; using std::string; using std::vector; +using robin_hood::unordered_flat_map; namespace Theme { extern std::filesystem::path theme_dir; @@ -53,9 +54,9 @@ namespace Theme { //* Set current theme from current "color_theme" value in config void setTheme(); - extern std::unordered_map colors; - extern std::unordered_map> rgbs; - extern std::unordered_map> gradients; + extern unordered_flat_map colors; + extern unordered_flat_map> rgbs; + extern unordered_flat_map> gradients; //* Return escape code for color inline const string& c(const string& name) { return colors.at(name); } diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index aaa9e54..88bf711 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -30,7 +30,7 @@ tab-size = 4 #include #include -#include "unordered_map" +#include "robin_hood.h" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" @@ -43,6 +43,7 @@ using std::flush; using std::max; using std::string_view; using std::to_string; +using robin_hood::unordered_flat_map; using namespace std::literals; // to use operator""s diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index c7b2cc3..c4d9670 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -31,10 +31,6 @@ tab-size = 4 #include #include #include -#include -#ifdef BTOP_DEBUG -#include -#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -150,35 +146,6 @@ namespace Term { void restore(); } -//* Simple logging implementation -namespace Logger { - const vector log_levels = { - "DISABLED", - "ERROR", - "WARNING", - "INFO", - "DEBUG", - }; - extern std::filesystem::path logfile; - - enum Level : size_t { - DISABLED = 0, - ERROR = 1, - WARNING = 2, - INFO = 3, - DEBUG = 4, - }; - - //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" - void set(const string& level); - - void log_write(const Level level, const string& msg); - inline void error(const string msg) { log_write(ERROR, msg); } - inline void warning(const string msg) { log_write(WARNING, msg); } - inline void info(const string msg) { log_write(INFO, msg); } - inline void debug(const string msg) { log_write(DEBUG, msg); } -} - //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Tools { @@ -337,50 +304,6 @@ namespace Tools { //* Add std::string operator * : Repeat string number of times std::string operator*(const string& str, int64_t n); - template -#ifdef BTOP_DEBUG - T safeVal(const std::unordered_map& map, const K& key, T fallback = T(), std::source_location loc = std::source_location::current()) { - if (map.contains(key)) { - return map.at(key); - } else { - Logger::error(fmt::format("safeVal() called with invalid key: [{}] in file: {} on line: {}", key, loc.file_name(), loc.line())); - return fallback; - } - }; -#else - T safeVal(const std::unordered_map& map, const K& key, T fallback = T()) { - if (map.contains(key)) { - return map.at(key); - } else { - Logger::error(fmt::format("safeVal() called with invalid key: [{}] (Compile btop with DEBUG=true for more extensive logging!)", key)); - return fallback; - } - }; -#endif - - template -#ifdef BTOP_DEBUG - T safeVal(const std::vector& vec, const size_t& index, T fallback = T(), std::source_location loc = std::source_location::current()) { - if (index < vec.size()) { - return vec.at(index); - } else { - Logger::error(fmt::format("safeVal() called with invalid index: [{}] in file: {} on line: {}", index, loc.file_name(), loc.line())); - return fallback; - } - }; -#else - T safeVal(const std::vector& vec, const size_t& index, T fallback = T()) { - if (index < vec.size()) { - return vec.at(index); - } else { - Logger::error(fmt::format("safeVal() called with invalid index: [{}] (Compile btop with DEBUG=true for more extensive logging!)", index)); - return fallback; - } - }; -#endif - - - //* Return current time in format string strf_time(const string& strf); @@ -419,6 +342,35 @@ namespace Tools { auto celsius_to(const long long& celsius, const string& scale) -> tuple; } +//* Simple logging implementation +namespace Logger { + const vector log_levels = { + "DISABLED", + "ERROR", + "WARNING", + "INFO", + "DEBUG", + }; + extern std::filesystem::path logfile; + + enum Level : size_t { + DISABLED = 0, + ERROR = 1, + WARNING = 2, + INFO = 3, + DEBUG = 4, + }; + + //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" + void set(const string& level); + + void log_write(const Level level, const string& msg); + inline void error(const string msg) { log_write(ERROR, msg); } + inline void warning(const string msg) { log_write(WARNING, msg); } + inline void info(const string msg) { log_write(INFO, msg); } + inline void debug(const string msg) { log_write(DEBUG, msg); } +} + namespace Tools { //* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running //* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 670acdd..7f93322 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - std::unordered_map core_mapping; + unordered_flat_map core_mapping; } // namespace Cpu namespace Mem { @@ -204,7 +204,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - std::unordered_map cpu_old = { + unordered_flat_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -323,8 +323,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz } - auto get_core_mapping() -> std::unordered_map { - std::unordered_map core_map; + auto get_core_mapping() -> unordered_flat_map { + unordered_flat_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -557,7 +557,7 @@ namespace Mem { } } - void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { + void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { // this bit is for 'regular' mounts static struct statinfo cur; long double etime = 0; @@ -691,7 +691,7 @@ namespace Mem { } if (show_disks) { - std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -807,13 +807,13 @@ namespace Mem { } // namespace Mem namespace Net { - std::unordered_map current_net; + unordered_flat_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; - std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; + unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; + unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -892,7 +892,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - std::unordered_map> ifstats; + unordered_flat_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1037,7 +1037,7 @@ namespace Net { namespace Proc { vector current_procs; - std::unordered_map uid_user; + unordered_flat_map uid_user; string current_sort; string current_filter; bool current_rev = false; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 1a8dbb4..33a0be8 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -17,8 +17,7 @@ tab-size = 4 */ #include -#include -#include +#include #include #include #include @@ -32,8 +31,6 @@ tab-size = 4 #include #include #include -#include -#include #if defined(RSMI_STATIC) #include @@ -97,10 +94,10 @@ namespace Cpu { int64_t crit{}; // defaults to 0 }; - std::unordered_map found_sensors; + unordered_flat_map found_sensors; string cpu_sensor; vector core_sensors; - std::unordered_map core_mapping; + unordered_flat_map core_mapping; } namespace Gpu { @@ -301,7 +298,7 @@ namespace Cpu { "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s }; - std::unordered_map cpu_old = { + unordered_flat_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -598,8 +595,8 @@ namespace Cpu { return cpuhz; } - auto get_core_mapping() -> std::unordered_map { - std::unordered_map core_map; + auto get_core_mapping() -> unordered_flat_map { + unordered_flat_map core_map; if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo @@ -672,7 +669,7 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0, ""}; static string auto_sel; - static std::unordered_map batteries; + static unordered_flat_map batteries; //? Get paths to needed files and check for valid values on first run if (batteries.empty() and has_battery) { @@ -1616,7 +1613,7 @@ namespace Mem { auto only_physical = Config::getB("only_physical"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; - static std::unordered_map>> disks_stats_promises; + static unordered_flat_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -2051,13 +2048,13 @@ namespace Mem { } namespace Net { - std::unordered_map current_net; + unordered_flat_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors{}; // defaults to 0 - std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; - std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; + unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; + unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; uint64_t timestamp{}; // defaults to 0 @@ -2196,6 +2193,7 @@ namespace Net { else it++; } + net.compact(); } timestamp = new_timestamp; @@ -2265,7 +2263,7 @@ namespace Net { namespace Proc { vector current_procs; - std::unordered_map uid_user; + unordered_flat_map uid_user; string current_sort; string current_filter; bool current_rev{}; // defaults to false @@ -2280,7 +2278,7 @@ namespace Proc { detail_container detailed; constexpr size_t KTHREADD = 2; - static std::unordered_set kernels_procs = {KTHREADD}; + static robin_hood::unordered_set kernels_procs = {KTHREADD}; //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 95ab322..681afc4 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -98,7 +98,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - std::unordered_map core_mapping; + unordered_flat_map core_mapping; } // namespace Cpu namespace Mem { @@ -194,7 +194,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - std::unordered_map cpu_old = { + unordered_flat_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -337,8 +337,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3); } - auto get_core_mapping() -> std::unordered_map { - std::unordered_map core_map; + auto get_core_mapping() -> unordered_flat_map { + unordered_flat_map core_map; if (cpu_temp_only) return core_map; natural_t cpu_count; @@ -599,7 +599,7 @@ namespace Mem { io_object_t &object; }; - void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { + void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { io_registry_entry_t drive; io_iterator_t drive_list; @@ -716,7 +716,7 @@ namespace Mem { } if (show_disks) { - std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -829,13 +829,13 @@ namespace Mem { } // namespace Mem namespace Net { - std::unordered_map current_net; + unordered_flat_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; - std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; + unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; + unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -912,7 +912,7 @@ namespace Net { } // else, ignoring family==AF_LINK (see man 3 getifaddrs) } - std::unordered_map> ifstats; + unordered_flat_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -1057,7 +1057,7 @@ namespace Net { namespace Proc { vector current_procs; - std::unordered_map uid_user; + unordered_flat_map uid_user; string current_sort; string current_filter; bool current_rev = false; From 3a8ceacaf89e7443014613dbc888b8b1657832fe Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 02:37:32 +0100 Subject: [PATCH 25/91] Fix call to compact and missing utility include --- src/freebsd/btop_collect.cpp | 2 +- src/osx/btop_collect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 670acdd..2a7a7f3 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -58,6 +58,7 @@ tab-size = 4 #include #include #include +#include #include "../btop_config.hpp" #include "../btop_shared.hpp" @@ -966,7 +967,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 95ab322..76b63c0 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -45,6 +45,7 @@ tab-size = 4 #include // for inet_ntop #include #include +#include #include #include @@ -986,7 +987,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; From f836233b64aeee1513be9dc74c4dc60b6248e578 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 02:49:24 +0100 Subject: [PATCH 26/91] Remove robin_hood.h --- include/robin_hood.h | 2544 ------------------------------------------ 1 file changed, 2544 deletions(-) delete mode 100644 include/robin_hood.h diff --git a/include/robin_hood.h b/include/robin_hood.h deleted file mode 100644 index 0af031f..0000000 --- a/include/robin_hood.h +++ /dev/null @@ -1,2544 +0,0 @@ -// ______ _____ ______ _________ -// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / -// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / -// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / -// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ -// _/_____/ -// -// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 -// https://github.com/martinus/robin-hood-hashing -// -// Licensed under the MIT License . -// SPDX-License-Identifier: MIT -// Copyright (c) 2018-2021 Martin Ankerl -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef ROBIN_HOOD_H_INCLUDED -#define ROBIN_HOOD_H_INCLUDED - -// see https://semver.org/ -#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes -#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes - -#include -#include -#include -#include -#include -#include // only to support hash of smart pointers -#include -#include -#include -#include -#if __cplusplus >= 201703L -# include -#endif - -// #define ROBIN_HOOD_LOG_ENABLED -#ifdef ROBIN_HOOD_LOG_ENABLED -# include -# define ROBIN_HOOD_LOG(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_LOG(x) -#endif - -// #define ROBIN_HOOD_TRACE_ENABLED -#ifdef ROBIN_HOOD_TRACE_ENABLED -# include -# define ROBIN_HOOD_TRACE(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_TRACE(x) -#endif - -// #define ROBIN_HOOD_COUNT_ENABLED -#ifdef ROBIN_HOOD_COUNT_ENABLED -# include -# define ROBIN_HOOD_COUNT(x) ++counts().x; -namespace robin_hood { -struct Counts { - uint64_t shiftUp{}; - uint64_t shiftDown{}; -}; -inline std::ostream& operator<<(std::ostream& os, Counts const& c) { - return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; -} - -static Counts& counts() { - static Counts counts{}; - return counts; -} -} // namespace robin_hood -#else -# define ROBIN_HOOD_COUNT(x) -#endif - -// all non-argument macros should use this facility. See -// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() - -// mark unused members with this macro -#define ROBIN_HOOD_UNUSED(identifier) - -// bitness -#if SIZE_MAX == UINT32_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 -#elif SIZE_MAX == UINT64_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 -#else -# error Unsupported bitness -#endif - -// endianess -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#endif - -// inline -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) -#endif - -// exceptions -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 -#endif - -// count leading/trailing bits -#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) -# ifdef _MSC_VER -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 -# endif -# include -# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ - [](size_t mask) noexcept -> int { \ - unsigned long index; \ - return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ - : ROBIN_HOOD(BITNESS); \ - }(x) -# else -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll -# endif -# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) -# endif -#endif - -// fallthrough -#ifndef __has_cpp_attribute // For backwards compatibility -# define __has_cpp_attribute(x) 0 -#endif -#if __has_cpp_attribute(clang::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] -#elif __has_cpp_attribute(gnu::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() -#endif - -// likely/unlikely -#ifdef _MSC_VER -# define ROBIN_HOOD_LIKELY(condition) condition -# define ROBIN_HOOD_UNLIKELY(condition) condition -#else -# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) -# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) -#endif - -// detect if native wchar_t type is availiable in MSVC -#ifdef _MSC_VER -# ifdef _NATIVE_WCHAR_T_DEFINED -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -#endif - -// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr -#ifdef _MSC_VER -# if _MSC_VER <= 1900 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -#endif - -// workaround missing "is_trivially_copyable" in g++ < 5.0 -// See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) -#else -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value -#endif - -// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() -#endif - -namespace robin_hood { - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) -# define ROBIN_HOOD_STD std -#else - -// c++11 compatibility layer -namespace ROBIN_HOOD_STD { -template -struct alignment_of - : std::integral_constant::type)> {}; - -template -class integer_sequence { -public: - using value_type = T; - static_assert(std::is_integral::value, "not integral type"); - static constexpr std::size_t size() noexcept { - return sizeof...(Ints); - } -}; -template -using index_sequence = integer_sequence; - -namespace detail_ { -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); - - template - struct IntSeqCombiner; - - template - struct IntSeqCombiner, integer_sequence> { - using TResult = integer_sequence; - }; - - using TResult = - typename IntSeqCombiner::TResult, - typename IntSeqImpl::TResult>::TResult; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; -} // namespace detail_ - -template -using make_integer_sequence = typename detail_::IntSeqImpl::TResult; - -template -using make_index_sequence = make_integer_sequence; - -template -using index_sequence_for = make_index_sequence; - -} // namespace ROBIN_HOOD_STD - -#endif - -namespace detail { - -// make sure we static_cast to the correct type for hash_int -#if ROBIN_HOOD(BITNESS) == 64 -using SizeT = uint64_t; -#else -using SizeT = uint32_t; -#endif - -template -T rotr(T x, unsigned k) { - return (x >> k) | (x << (8U * sizeof(T) - k)); -} - -// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to -// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with -// care! -template -inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { - return reinterpret_cast(ptr); -} - -template -inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { - return reinterpret_cast(ptr); -} - -// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other -// inlinings more difficult. Throws are also generally the slow path. -template -[[noreturn]] ROBIN_HOOD(NOINLINE) -#if ROBIN_HOOD(HAS_EXCEPTIONS) - void doThrow(Args&&... args) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - throw E(std::forward(args)...); -} -#else - void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { - abort(); -} -#endif - -template -T* assertNotNull(T* t, Args&&... args) { - if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { - doThrow(std::forward(args)...); - } - return t; -} - -template -inline T unaligned_load(void const* ptr) noexcept { - // using memcpy so we don't get into unaligned load problems. - // compiler should optimize this very well anyways. - T t; - std::memcpy(&t, ptr, sizeof(T)); - return t; -} - -// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, -// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a -// pointer. -template -class BulkPoolAllocator { -public: - BulkPoolAllocator() noexcept = default; - - // does not copy anything, just creates a new allocator. - BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept - : mHead(nullptr) - , mListForFree(nullptr) {} - - BulkPoolAllocator(BulkPoolAllocator&& o) noexcept - : mHead(o.mHead) - , mListForFree(o.mListForFree) { - o.mListForFree = nullptr; - o.mHead = nullptr; - } - - BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { - reset(); - mHead = o.mHead; - mListForFree = o.mListForFree; - o.mListForFree = nullptr; - o.mHead = nullptr; - return *this; - } - - BulkPoolAllocator& - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { - // does not do anything - return *this; - } - - ~BulkPoolAllocator() noexcept { - reset(); - } - - // Deallocates all allocated memory. - void reset() noexcept { - while (mListForFree) { - T* tmp = *mListForFree; - ROBIN_HOOD_LOG("std::free") - std::free(mListForFree); - mListForFree = reinterpret_cast_no_cast_align_warning(tmp); - } - mHead = nullptr; - } - - // allocates, but does NOT initialize. Use in-place new constructor, e.g. - // T* obj = pool.allocate(); - // ::new (static_cast(obj)) T(); - T* allocate() { - T* tmp = mHead; - if (!tmp) { - tmp = performAllocation(); - } - - mHead = *reinterpret_cast_no_cast_align_warning(tmp); - return tmp; - } - - // does not actually deallocate but puts it in store. - // make sure you have already called the destructor! e.g. with - // obj->~T(); - // pool.deallocate(obj); - void deallocate(T* obj) noexcept { - *reinterpret_cast_no_cast_align_warning(obj) = mHead; - mHead = obj; - } - - // Adds an already allocated block of memory to the allocator. This allocator is from now on - // responsible for freeing the data (with free()). If the provided data is not large enough to - // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. - void addOrFree(void* ptr, const size_t numBytes) noexcept { - // calculate number of available elements in ptr - if (numBytes < ALIGNMENT + ALIGNED_SIZE) { - // not enough data for at least one element. Free and return. - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } else { - ROBIN_HOOD_LOG("add to buffer") - add(ptr, numBytes); - } - } - - void swap(BulkPoolAllocator& other) noexcept { - using std::swap; - swap(mHead, other.mHead); - swap(mListForFree, other.mListForFree); - } - -private: - // iterates the list of allocated memory to calculate how many to alloc next. - // Recalculating this each time saves us a size_t member. - // This ignores the fact that memory blocks might have been added manually with addOrFree. In - // practice, this should not matter much. - ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { - auto tmp = mListForFree; - size_t numAllocs = MinNumAllocs; - - while (numAllocs * 2 <= MaxNumAllocs && tmp) { - auto x = reinterpret_cast(tmp); - tmp = *x; - numAllocs *= 2; - } - - return numAllocs; - } - - // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). - void add(void* ptr, const size_t numBytes) noexcept { - const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; - - auto data = reinterpret_cast(ptr); - - // link free list - auto x = reinterpret_cast(data); - *x = mListForFree; - mListForFree = data; - - // create linked list for newly allocated data - auto* const headT = - reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); - - auto* const head = reinterpret_cast(headT); - - // Visual Studio compiler automatically unrolls this loop, which is pretty cool - for (size_t i = 0; i < numElements; ++i) { - *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = - head + (i + 1) * ALIGNED_SIZE; - } - - // last one points to 0 - *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = - mHead; - mHead = headT; - } - - // Called when no memory is available (mHead == 0). - // Don't inline this slow path. - ROBIN_HOOD(NOINLINE) T* performAllocation() { - size_t const numElementsToAlloc = calcNumElementsToAlloc(); - - // alloc new memory: [prev |T, T, ... T] - size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; - ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE - << " * " << numElementsToAlloc) - add(assertNotNull(std::malloc(bytes)), bytes); - return mHead; - } - - // enforce byte alignment of the T's -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) - static constexpr size_t ALIGNMENT = - (std::max)(std::alignment_of::value, std::alignment_of::value); -#else - static const size_t ALIGNMENT = - (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) - ? ROBIN_HOOD_STD::alignment_of::value - : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround -#endif - - static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; - - static_assert(MinNumAllocs >= 1, "MinNumAllocs"); - static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); - static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); - static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); - static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); - - T* mHead{nullptr}; - T** mListForFree{nullptr}; -}; - -template -struct NodeAllocator; - -// dummy allocator that does nothing -template -struct NodeAllocator { - - // we are not using the data, so just free it. - void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } -}; - -template -struct NodeAllocator : public BulkPoolAllocator {}; - -// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making -// my own here. -namespace swappable { -#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) -using std::swap; -template -struct nothrow { - static const bool value = noexcept(swap(std::declval(), std::declval())); -}; -#else -template -struct nothrow { - static const bool value = std::is_nothrow_swappable::value; -}; -#endif -} // namespace swappable - -} // namespace detail - -struct is_transparent_tag {}; - -// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, -// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is -// also tested. -template -struct pair { - using first_type = T1; - using second_type = T2; - - template ::value && - std::is_default_constructible::value>::type> - constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) - : first() - , second() {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair const& o) noexcept( - noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) - : first(o.first) - , second(o.second) {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair&& o) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(o.first)) - , second(std::move(o.second)) {} - - constexpr pair(T1&& a, T2&& b) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(a)) - , second(std::move(b)) {} - - template - constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( - std::declval()))) && noexcept(T2(std::forward(std::declval())))) - : first(std::forward(a)) - , second(std::forward(b)) {} - - template - // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" - // if this constructor is constexpr -#if !ROBIN_HOOD(BROKEN_CONSTEXPR) - constexpr -#endif - pair(std::piecewise_construct_t /*unused*/, std::tuple a, - std::tuple - b) noexcept(noexcept(pair(std::declval&>(), - std::declval&>(), - ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()))) - : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()) { - } - - // constructor called from the std::piecewise_construct_t ctor - template - pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( - noexcept(T1(std::forward(std::get( - std::declval&>()))...)) && noexcept(T2(std:: - forward(std::get( - std::declval&>()))...))) - : first(std::forward(std::get(a))...) - , second(std::forward(std::get(b))...) { - // make visual studio compiler happy about warning about unused a & b. - // Visual studio's pair implementation disables warning 4100. - (void)a; - (void)b; - } - - void swap(pair& o) noexcept((detail::swappable::nothrow::value) && - (detail::swappable::nothrow::value)) { - using std::swap; - swap(first, o.first); - swap(second, o.second); - } - - T1 first; // NOLINT(misc-non-private-member-variables-in-classes) - T2 second; // NOLINT(misc-non-private-member-variables-in-classes) -}; - -template -inline void swap(pair& a, pair& b) noexcept( - noexcept(std::declval&>().swap(std::declval&>()))) { - a.swap(b); -} - -template -inline constexpr bool operator==(pair const& x, pair const& y) { - return (x.first == y.first) && (x.second == y.second); -} -template -inline constexpr bool operator!=(pair const& x, pair const& y) { - return !(x == y); -} -template -inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( - std::declval() < std::declval()) && noexcept(std::declval() < - std::declval())) { - return x.first < y.first || (!(y.first < x.first) && x.second < y.second); -} -template -inline constexpr bool operator>(pair const& x, pair const& y) { - return y < x; -} -template -inline constexpr bool operator<=(pair const& x, pair const& y) { - return !(x > y); -} -template -inline constexpr bool operator>=(pair const& x, pair const& y) { - return !(x < y); -} - -inline size_t hash_bytes(void const* ptr, size_t len) noexcept { - static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); - static constexpr uint64_t seed = UINT64_C(0xe17a1465); - static constexpr unsigned int r = 47; - - auto const* const data64 = static_cast(ptr); - uint64_t h = seed ^ (len * m); - - size_t const n_blocks = len / 8; - for (size_t i = 0; i < n_blocks; ++i) { - auto k = detail::unaligned_load(data64 + i); - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - auto const* const data8 = reinterpret_cast(data64 + n_blocks); - switch (len & 7U) { - case 7: - h ^= static_cast(data8[6]) << 48U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 6: - h ^= static_cast(data8[5]) << 40U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 5: - h ^= static_cast(data8[4]) << 32U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 4: - h ^= static_cast(data8[3]) << 24U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 3: - h ^= static_cast(data8[2]) << 16U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 2: - h ^= static_cast(data8[1]) << 8U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 1: - h ^= static_cast(data8[0]); - h *= m; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - default: - break; - } - - h ^= h >> r; - - // not doing the final step here, because this will be done by keyToIdx anyways - // h *= m; - // h ^= h >> r; - return static_cast(h); -} - -inline size_t hash_int(uint64_t x) noexcept { - // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, - // and doesn't need any special 128bit operations. - x ^= x >> 33U; - x *= UINT64_C(0xff51afd7ed558ccd); - x ^= x >> 33U; - - // not doing the final step here, because this will be done by keyToIdx anyways - // x *= UINT64_C(0xc4ceb9fe1a85ec53); - // x ^= x >> 33U; - return static_cast(x); -} - -// A thin wrapper around std::hash, performing an additional simple mixing step of the result. -template -struct hash : public std::hash { - size_t operator()(T const& obj) const - noexcept(noexcept(std::declval>().operator()(std::declval()))) { - // call base hash - auto result = std::hash::operator()(obj); - // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); - } -}; - -template -struct hash> { - size_t operator()(std::basic_string const& str) const noexcept { - return hash_bytes(str.data(), sizeof(CharT) * str.size()); - } -}; - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -template -struct hash> { - size_t operator()(std::basic_string_view const& sv) const noexcept { - return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); - } -}; -#endif - -template -struct hash { - size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); - } -}; - -template -struct hash> { - size_t operator()(std::unique_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash> { - size_t operator()(std::shared_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash::value>::type> { - size_t operator()(Enum e) const noexcept { - using Underlying = typename std::underlying_type::type; - return hash{}(static_cast(e)); - } -}; - -#define ROBIN_HOOD_HASH_INT(T) \ - template <> \ - struct hash { \ - size_t operator()(T const& obj) const noexcept { \ - return hash_int(static_cast(obj)); \ - } \ - } - -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif -// see https://en.cppreference.com/w/cpp/utility/hash -ROBIN_HOOD_HASH_INT(bool); -ROBIN_HOOD_HASH_INT(char); -ROBIN_HOOD_HASH_INT(signed char); -ROBIN_HOOD_HASH_INT(unsigned char); -ROBIN_HOOD_HASH_INT(char16_t); -ROBIN_HOOD_HASH_INT(char32_t); -#if ROBIN_HOOD(HAS_NATIVE_WCHART) -ROBIN_HOOD_HASH_INT(wchar_t); -#endif -ROBIN_HOOD_HASH_INT(short); -ROBIN_HOOD_HASH_INT(unsigned short); -ROBIN_HOOD_HASH_INT(int); -ROBIN_HOOD_HASH_INT(unsigned int); -ROBIN_HOOD_HASH_INT(long); -ROBIN_HOOD_HASH_INT(long long); -ROBIN_HOOD_HASH_INT(unsigned long); -ROBIN_HOOD_HASH_INT(unsigned long long); -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif -namespace detail { - -template -struct void_type { - using type = void; -}; - -template -struct has_is_transparent : public std::false_type {}; - -template -struct has_is_transparent::type> - : public std::true_type {}; - -// using wrapper classes for hash and key_equal prevents the diamond problem when the same type -// is used. see https://stackoverflow.com/a/28771920/48181 -template -struct WrapHash : public T { - WrapHash() = default; - explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -template -struct WrapKeyEqual : public T { - WrapKeyEqual() = default; - explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -// A highly optimized hashmap implementation, using the Robin Hood algorithm. -// -// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but -// be about 2x faster in most cases and require much less allocations. -// -// This implementation uses the following memory layout: -// -// [Node, Node, ... Node | info, info, ... infoSentinel ] -// -// * Node: either a DataNode that directly has the std::pair as member, -// or a DataNode with a pointer to std::pair. Which DataNode representation to use -// depends on how fast the swap() operation is. Heuristically, this is automatically choosen -// based on sizeof(). there are always 2^n Nodes. -// -// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. -// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the -// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it -// actually belongs to the previous position and was pushed out because that place is already -// taken. -// -// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the -// need for a idx variable. -// -// According to STL, order of templates has effect on throughput. That's why I've moved the -// boolean to the front. -// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ -template -class Table - : public WrapHash, - public WrapKeyEqual, - detail::NodeAllocator< - typename std::conditional< - std::is_void::value, Key, - robin_hood::pair::type, T>>::type, - 4, 16384, IsFlat> { -public: - static constexpr bool is_flat = IsFlat; - static constexpr bool is_map = !std::is_void::value; - static constexpr bool is_set = !is_map; - static constexpr bool is_transparent = - has_is_transparent::value && has_is_transparent::value; - - using key_type = Key; - using mapped_type = T; - using value_type = typename std::conditional< - is_set, Key, - robin_hood::pair::type, T>>::type; - using size_type = size_t; - using hasher = Hash; - using key_equal = KeyEqual; - using Self = Table; - -private: - static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, - "MaxLoadFactor100 needs to be >10 && < 100"); - - using WHash = WrapHash; - using WKeyEqual = WrapKeyEqual; - - // configuration defaults - - // make sure we have 8 elements, needed to quickly rehash mInfo - static constexpr size_t InitialNumElements = sizeof(uint64_t); - static constexpr uint32_t InitialInfoNumBits = 5; - static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; - static constexpr size_t InfoMask = InitialInfoInc - 1U; - static constexpr uint8_t InitialInfoHashShift = 0; - using DataPool = detail::NodeAllocator; - - // type needs to be wider than uint8_t. - using InfoType = uint32_t; - - // DataNode //////////////////////////////////////////////////////// - - // Primary template for the data node. We have special implementations for small and big - // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these - // on the heap so swap merely swaps a pointer. - template - class DataNode {}; - - // Small: just allocate on the stack. - template - class DataNode final { - public: - template - explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( - noexcept(value_type(std::forward(args)...))) - : mData(std::forward(args)...) {} - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( - std::is_nothrow_move_constructible::value) - : mData(std::move(n.mData)) {} - - // doesn't do anything - void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} - void destroyDoNotDeallocate() noexcept {} - - value_type const* operator->() const noexcept { - return &mData; - } - value_type* operator->() noexcept { - return &mData; - } - - const value_type& operator*() const noexcept { - return mData; - } - - value_type& operator*() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData.second; - } - - void swap(DataNode& o) noexcept( - noexcept(std::declval().swap(std::declval()))) { - mData.swap(o.mData); - } - - private: - value_type mData; - }; - - // big object: allocate on heap. - template - class DataNode { - public: - template - explicit DataNode(M& map, Args&&... args) - : mData(map.allocate()) { - ::new (static_cast(mData)) value_type(std::forward(args)...); - } - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept - : mData(std::move(n.mData)) {} - - void destroy(M& map) noexcept { - // don't deallocate, just put it into list of datapool. - mData->~value_type(); - map.deallocate(mData); - } - - void destroyDoNotDeallocate() noexcept { - mData->~value_type(); - } - - value_type const* operator->() const noexcept { - return mData; - } - - value_type* operator->() noexcept { - return mData; - } - - const value_type& operator*() const { - return *mData; - } - - value_type& operator*() { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData->second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData->second; - } - - void swap(DataNode& o) noexcept { - using std::swap; - swap(mData, o.mData); - } - - private: - value_type* mData; - }; - - using Node = DataNode; - - // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { - return n.getFirst(); - } - - // in case we have void mapped_type, we are not using a pair, thus we just route k through. - // No need to disable this because it's just not used if not applicable. - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { - return k; - } - - // in case we have non-void mapped_type, we have a standard robin_hood::pair - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, key_type const&>::type - getFirstConst(value_type const& vt) const noexcept { - return vt.first; - } - - // Cloner ////////////////////////////////////////////////////////// - - template - struct Cloner; - - // fast path: Just copy data, without allocating anything. - template - struct Cloner { - void operator()(M const& source, M& target) const { - auto const* const src = reinterpret_cast(source.mKeyVals); - auto* tgt = reinterpret_cast(target.mKeyVals); - auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); - std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); - } - }; - - template - struct Cloner { - void operator()(M const& s, M& t) const { - auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); - std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); - - for (size_t i = 0; i < numElementsWithBuffer; ++i) { - if (t.mInfo[i]) { - ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); - } - } - } - }; - - // Destroyer /////////////////////////////////////////////////////// - - template - struct Destroyer {}; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - } - }; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroy(m); - n.~Node(); - } - } - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroyDoNotDeallocate(); - n.~Node(); - } - } - } - }; - - // Iter //////////////////////////////////////////////////////////// - - struct fast_forward_tag {}; - - // generic iterator for both const_iterator and iterator. - template - // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) - class Iter { - private: - using NodePtr = typename std::conditional::type; - - public: - using difference_type = std::ptrdiff_t; - using value_type = typename Self::value_type; - using reference = typename std::conditional::type; - using pointer = typename std::conditional::type; - using iterator_category = std::forward_iterator_tag; - - // default constructed iterator can be compared to itself, but WON'T return true when - // compared to end(). - Iter() = default; - - // Rule of zero: nothing specified. The conversion constructor is only enabled for - // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. - - // Conversion constructor from iterator to const_iterator. - template ::type> - // NOLINTNEXTLINE(hicpp-explicit-conversions) - Iter(Iter const& other) noexcept - : mKeyVals(other.mKeyVals) - , mInfo(other.mInfo) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr, - fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) { - fastForward(); - } - - template ::type> - Iter& operator=(Iter const& other) noexcept { - mKeyVals = other.mKeyVals; - mInfo = other.mInfo; - return *this; - } - - // prefix increment. Undefined behavior if we are at end()! - Iter& operator++() noexcept { - mInfo++; - mKeyVals++; - fastForward(); - return *this; - } - - Iter operator++(int) noexcept { - Iter tmp = *this; - ++(*this); - return tmp; - } - - reference operator*() const { - return **mKeyVals; - } - - pointer operator->() const { - return &**mKeyVals; - } - - template - bool operator==(Iter const& o) const noexcept { - return mKeyVals == o.mKeyVals; - } - - template - bool operator!=(Iter const& o) const noexcept { - return mKeyVals != o.mKeyVals; - } - - private: - // fast forward to the next non-free info byte - // I've tried a few variants that don't depend on intrinsics, but unfortunately they are - // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. - void fastForward() noexcept { - size_t n = 0; - while (0U == (n = detail::unaligned_load(mInfo))) { - mInfo += sizeof(size_t); - mKeyVals += sizeof(size_t); - } -#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) - // we know for certain that within the next 8 bytes we'll find a non-zero one. - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 4; - mKeyVals += 4; - } - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 2; - mKeyVals += 2; - } - if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { - mInfo += 1; - mKeyVals += 1; - } -#else -# if ROBIN_HOOD(LITTLE_ENDIAN) - auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; -# else - auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; -# endif - mInfo += inc; - mKeyVals += inc; -#endif - } - - friend class Table; - NodePtr mKeyVals{nullptr}; - uint8_t const* mInfo{nullptr}; - }; - - //////////////////////////////////////////////////////////////////// - - // highly performance relevant code. - // Lower bits are used for indexing into the array (2^n size) - // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. - template - void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { - // In addition to whatever hash is used, add another mul & shift so we get better hashing. - // This serves as a bad hash prevention, if the given data is - // badly mixed. - auto h = static_cast(WHash::operator()(key)); - - h *= mHashMultiplier; - h ^= h >> 33U; - - // the lower InitialInfoNumBits are reserved for info. - *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); - *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; - } - - // forwards the index by one, wrapping around at the end - void next(InfoType* info, size_t* idx) const noexcept { - *idx = *idx + 1; - *info += mInfoInc; - } - - void nextWhileLess(InfoType* info, size_t* idx) const noexcept { - // unrolling this by hand did not bring any speedups. - while (*info < mInfo[*idx]) { - next(info, idx); - } - } - - // Shift everything up by one element. Tries to move stuff around. - void - shiftUp(size_t startIdx, - size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { - auto idx = startIdx; - ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); - while (--idx != insertion_idx) { - mKeyVals[idx] = std::move(mKeyVals[idx - 1]); - } - - idx = startIdx; - while (idx != insertion_idx) { - ROBIN_HOOD_COUNT(shiftUp) - mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); - if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - --idx; - } - } - - void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { - // until we find one that is either empty or has zero offset. - // TODO(martinus) we don't need to move everything, just the last one for the same - // bucket. - mKeyVals[idx].destroy(*this); - - // until we find one that is either empty or has zero offset. - while (mInfo[idx + 1] >= 2 * mInfoInc) { - ROBIN_HOOD_COUNT(shiftDown) - mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); - mKeyVals[idx] = std::move(mKeyVals[idx + 1]); - ++idx; - } - - mInfo[idx] = 0; - // don't destroy, we've moved it - // mKeyVals[idx].destroy(*this); - mKeyVals[idx].~Node(); - } - - // copy of find(), except that it returns iterator instead of const_iterator. - template - ROBIN_HOOD(NODISCARD) - size_t findIdx(Other const& key) const { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - do { - // unrolling this twice gives a bit of a speedup. More unrolling did not help. - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found! - return mMask == 0 ? 0 - : static_cast(std::distance( - mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); - } - - void cloneData(const Table& o) { - Cloner()(o, *this); - } - - // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. - // @return True on success, false if something went wrong - void insert_move(Node&& keyval) { - // we don't retry, fail if overflowing - // don't need to check max num elements - if (0 == mMaxNumElementsAllowed && !try_increase_info()) { - throwOverflowError(); - } - - size_t idx{}; - InfoType info{}; - keyToIdx(keyval.getFirst(), &idx, &info); - - // skip forward. Use <= because we are certain that the element is not there. - while (info <= mInfo[idx]) { - idx = idx + 1; - info += mInfoInc; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = static_cast(info); - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - ::new (static_cast(&l)) Node(std::move(keyval)); - } else { - shiftUp(idx, insertion_idx); - l = std::move(keyval); - } - - // put at empty spot - mInfo[insertion_idx] = insertion_info; - - ++mNumElements; - } - -public: - using iterator = Iter; - using const_iterator = Iter; - - Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) - : WHash() - , WKeyEqual() { - ROBIN_HOOD_TRACE(this) - } - - // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. - // This tremendously speeds up ctor & dtor of a map that never receives an element. The - // penalty is payed at the first insert, and not before. Lookup of this empty map works - // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the - // standard, but we can ignore it. - explicit Table( - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - } - - template - Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(first, last); - } - - Table(std::initializer_list initlist, - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(initlist.begin(), initlist.end()); - } - - Table(Table&& o) noexcept - : WHash(std::move(static_cast(o))) - , WKeyEqual(std::move(static_cast(o))) - , DataPool(std::move(static_cast(o))) { - ROBIN_HOOD_TRACE(this) - if (o.mMask) { - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - // set other's mask to 0 so its destructor won't do anything - o.init(); - } - } - - Table& operator=(Table&& o) noexcept { - ROBIN_HOOD_TRACE(this) - if (&o != this) { - if (o.mMask) { - // only move stuff if the other map actually has some data - destroy(); - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - WHash::operator=(std::move(static_cast(o))); - WKeyEqual::operator=(std::move(static_cast(o))); - DataPool::operator=(std::move(static_cast(o))); - - o.init(); - - } else { - // nothing in the other map => just clear us. - clear(); - } - } - return *this; - } - - Table(const Table& o) - : WHash(static_cast(o)) - , WKeyEqual(static_cast(o)) - , DataPool(static_cast(o)) { - ROBIN_HOOD_TRACE(this) - if (!o.empty()) { - // not empty: create an exact copy. it is also possible to just iterate through all - // elements and insert them, but copying is probably faster. - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mHashMultiplier = o.mHashMultiplier; - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - // no need for calloc because clonData does memcpy - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - } - } - - // Creates a copy of the given map. Copy constructor of each entry is used. - // Not sure why clang-tidy thinks this doesn't handle self assignment, it does - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - Table& operator=(Table const& o) { - ROBIN_HOOD_TRACE(this) - if (&o == this) { - // prevent assigning of itself - return *this; - } - - // we keep using the old allocator and not assign the new one, because we want to keep - // the memory available. when it is the same size. - if (o.empty()) { - if (0 == mMask) { - // nothing to do, we are empty too - return *this; - } - - // not empty: destroy what we have there - // clear also resets mInfo to 0, that's sometimes not necessary. - destroy(); - init(); - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - - return *this; - } - - // clean up old stuff - Destroyer::value>{}.nodes(*this); - - if (mMask != o.mMask) { - // no luck: we don't have the same array size allocated, so we need to realloc. - if (0 != mMask) { - // only deallocate if we actually have data! - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - - // no need for calloc here because cloneData performs a memcpy. - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - // sentinel is set in cloneData - } - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - mHashMultiplier = o.mHashMultiplier; - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - - return *this; - } - - // Swaps everything between the two maps. - void swap(Table& o) { - ROBIN_HOOD_TRACE(this) - using std::swap; - swap(o, *this); - } - - // Clears all data, without resizing. - void clear() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - // don't do anything! also important because we don't want to write to - // DummyInfoByte::b, even though we would just write 0 to it. - return; - } - - Destroyer::value>{}.nodes(*this); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - // clear everything, then set the sentinel again - uint8_t const z = 0; - std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // Destroys the map and all it's contents. - ~Table() { - ROBIN_HOOD_TRACE(this) - destroy(); - } - - // Checks if both tables contain the same entries. Order is irrelevant. - bool operator==(const Table& other) const { - ROBIN_HOOD_TRACE(this) - if (other.size() != size()) { - return false; - } - for (auto const& otherEntry : other) { - if (!has(otherEntry)) { - return false; - } - } - - return true; - } - - bool operator!=(const Table& other) const { - ROBIN_HOOD_TRACE(this) - return !operator==(other); - } - - template - typename std::enable_if::value, Q&>::type operator[](const key_type& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(key), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(key), std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - typename std::enable_if::value, Q&>::type operator[](key_type&& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - void insert(Iter first, Iter last) { - for (; first != last; ++first) { - // value_type ctor needed because this might be called with std::pair's - insert(value_type(*first)); - } - } - - void insert(std::initializer_list ilist) { - for (auto&& vt : ilist) { - insert(std::move(vt)); - } - } - - template - std::pair emplace(Args&&... args) { - ROBIN_HOOD_TRACE(this) - Node n{*this, std::forward(args)...}; - auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); - switch (idxAndState.second) { - case InsertionState::key_found: - n.destroy(*this); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = std::move(n); - break; - - case InsertionState::overflow_error: - n.destroy(*this); - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - iterator emplace_hint(const_iterator position, Args&&... args) { - (void)position; - return emplace(std::forward(args)...).first; - } - - template - std::pair try_emplace(const key_type& key, Args&&... args) { - return try_emplace_impl(key, std::forward(args)...); - } - - template - std::pair try_emplace(key_type&& key, Args&&... args) { - return try_emplace_impl(std::move(key), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { - (void)hint; - return try_emplace_impl(key, std::forward(args)...).first; - } - - template - iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { - (void)hint; - return try_emplace_impl(std::move(key), std::forward(args)...).first; - } - - template - std::pair insert_or_assign(const key_type& key, Mapped&& obj) { - return insertOrAssignImpl(key, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type&& key, Mapped&& obj) { - return insertOrAssignImpl(std::move(key), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(key, std::forward(obj)).first; - } - - template - iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(std::move(key), std::forward(obj)).first; - } - - std::pair insert(const value_type& keyval) { - ROBIN_HOOD_TRACE(this) - return emplace(keyval); - } - - iterator insert(const_iterator hint, const value_type& keyval) { - (void)hint; - return emplace(keyval).first; - } - - std::pair insert(value_type&& keyval) { - return emplace(std::move(keyval)); - } - - iterator insert(const_iterator hint, value_type&& keyval) { - (void)hint; - return emplace(std::move(keyval)).first; - } - - // Returns 1 if key is found, 0 otherwise. - size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type count(const OtherKey& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - return 1U == count(key); - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type contains(const OtherKey& key) const { - return 1U == count(key); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q&>::type at(key_type const& key) { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q const&>::type at(key_type const& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type // NOLINT(modernize-use-nodiscard) - find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator find(const key_type& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type find(const OtherKey& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator begin() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - return end(); - } - return iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - const_iterator begin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cbegin(); - } - const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - if (empty()) { - return cend(); - } - return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - - iterator end() { - ROBIN_HOOD_TRACE(this) - // no need to supply valid info pointer: end() must not be dereferenced, and only node - // pointer is compared. - return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - const_iterator end() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cend(); - } - const_iterator cend() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - - iterator erase(const_iterator pos) { - ROBIN_HOOD_TRACE(this) - // its safe to perform const cast here - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); - } - - // Erases element at pos, returns iterator to the next element. - iterator erase(iterator pos) { - ROBIN_HOOD_TRACE(this) - // we assume that pos always points to a valid entry, and not end(). - auto const idx = static_cast(pos.mKeyVals - mKeyVals); - - shiftDown(idx); - --mNumElements; - - if (*pos.mInfo) { - // we've backward shifted, return this again - return pos; - } - - // no backward shift, return next element - return ++pos; - } - - size_t erase(const key_type& key) { - ROBIN_HOOD_TRACE(this) - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - // check while info matches with the source idx - do { - if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - shiftDown(idx); - --mNumElements; - return 1; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found to delete - return 0; - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // exactly the same as reserve(c). - void rehash(size_t c) { - // forces a reserve - reserve(c, true); - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. - void reserve(size_t c) { - // reserve, but don't force rehash - reserve(c, false); - } - - // If possible reallocates the map to a smaller one. This frees the underlying table. - // Does not do anything if load_factor is too large for decreasing the table's size. - void compact() { - ROBIN_HOOD_TRACE(this) - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (newSize < mMask + 1) { - rehashPowerOfTwo(newSize, true); - } - } - - size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return mNumElements; - } - - size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(-1); - } - - ROBIN_HOOD(NODISCARD) bool empty() const noexcept { - ROBIN_HOOD_TRACE(this) - return 0 == mNumElements; - } - - float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return MaxLoadFactor100 / 100.0F; - } - - // Average number of elements per bucket. Since we allow only 1 per bucket - float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(size()) / static_cast(mMask + 1); - } - - ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { - ROBIN_HOOD_TRACE(this) - return mMask; - } - - ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { - if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { - return maxElements * MaxLoadFactor100 / 100; - } - - // we might be a bit inprecise, but since maxElements is quite large that doesn't matter - return (maxElements / 100) * MaxLoadFactor100; - } - - ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { - // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load - // 64bit types. - return numElements + sizeof(uint64_t); - } - - ROBIN_HOOD(NODISCARD) - size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { - auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); - return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); - } - - // calculation only allowed for 2^n values - ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { -#if ROBIN_HOOD(BITNESS) == 64 - return numElements * sizeof(Node) + calcNumBytesInfo(numElements); -#else - // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. - auto const ne = static_cast(numElements); - auto const s = static_cast(sizeof(Node)); - auto const infos = static_cast(calcNumBytesInfo(numElements)); - - auto const total64 = ne * s + infos; - auto const total = static_cast(total64); - - if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { - throwOverflowError(); - } - return total; -#endif - } - -private: - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - auto it = find(e.first); - return it != end() && it->second == e.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - return find(e) != end(); - } - - void reserve(size_t c, bool forceRehash) { - ROBIN_HOOD_TRACE(this) - auto const minElementsAllowed = (std::max)(c, mNumElements); - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (forceRehash || newSize > mMask + 1) { - rehashPowerOfTwo(newSize, false); - } - } - - // reserves space for at least the specified number of elements. - // only works if numBuckets if power of two - // True on success, false otherwise - void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { - ROBIN_HOOD_TRACE(this) - - Node* const oldKeyVals = mKeyVals; - uint8_t const* const oldInfo = mInfo; - - const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - // resize operation: move stuff - initData(numBuckets); - if (oldMaxElementsWithBuffer > 1) { - for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { - if (oldInfo[i] != 0) { - // might throw an exception, which is really bad since we are in the middle of - // moving stuff. - insert_move(std::move(oldKeyVals[i])); - // destroy the node but DON'T destroy the data. - oldKeyVals[i].~Node(); - } - } - - // this check is not necessary as it's guarded by the previous if, but it helps - // silence g++'s overeager "attempt to free a non-heap object 'map' - // [-Werror=free-nonheap-object]" warning. - if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - // don't destroy old data: put it into the pool instead - if (forceFree) { - std::free(oldKeyVals); - } else { - DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); - } - } - } - } - - ROBIN_HOOD(NOINLINE) void throwOverflowError() const { -#if ROBIN_HOOD(HAS_EXCEPTIONS) - throw std::overflow_error("robin_hood::map overflow"); -#else - abort(); -#endif - } - - template - std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - mKeyVals[idxAndState.first].getSecond() = std::forward(obj); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - void initData(size_t max_elements) { - mNumElements = 0; - mMask = max_elements - 1; - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - - // malloc & zero mInfo. Faster than calloc everything. - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = reinterpret_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); - - // set sentinel - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; - - // Finds key, and if not already present prepares a spot where to pot the key & value. - // This potentially shifts nodes out of the way, updates mInfo and number of inserted - // elements, so the only operation left to do is create/assign a new node at that spot. - template - std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { - for (int i = 0; i < 256; ++i) { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - nextWhileLess(&info, &idx); - - // while we potentially have a match - while (info == mInfo[idx]) { - if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - // key already exists, do NOT insert. - // see http://en.cppreference.com/w/cpp/container/unordered_map/insert - return std::make_pair(idx, InsertionState::key_found); - } - next(&info, &idx); - } - - // unlikely that this evaluates to true - if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - if (!increase_size()) { - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - continue; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = info; - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - if (idx != insertion_idx) { - shiftUp(idx, insertion_idx); - } - // put at empty spot - mInfo[insertion_idx] = static_cast(insertion_info); - ++mNumElements; - return std::make_pair(insertion_idx, idx == insertion_idx - ? InsertionState::new_node - : InsertionState::overwrite_node); - } - - // enough attempts failed, so finally give up. - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - - bool try_increase_info() { - ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements - << ", maxNumElementsAllowed=" - << calcMaxNumElementsAllowed(mMask + 1)) - if (mInfoInc <= 2) { - // need to be > 2 so that shift works (otherwise undefined behavior!) - return false; - } - // we got space left, try to make info smaller - mInfoInc = static_cast(mInfoInc >> 1U); - - // remove one bit of the hash, leaving more space for the distance info. - // This is extremely fast because we can operate on 8 bytes at once. - ++mInfoHashShift; - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - for (size_t i = 0; i < numElementsWithBuffer; i += 8) { - auto val = unaligned_load(mInfo + i); - val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); - std::memcpy(mInfo + i, &val, sizeof(val)); - } - // update sentinel, which might have been cleared out! - mInfo[numElementsWithBuffer] = 1; - - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - return true; - } - - // True if resize was possible, false otherwise - bool increase_size() { - // nothing allocated yet? just allocate InitialNumElements - if (0 == mMask) { - initData(InitialNumElements); - return true; - } - - auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - if (mNumElements < maxNumElementsAllowed && try_increase_info()) { - return true; - } - - ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" - << maxNumElementsAllowed << ", load=" - << (static_cast(mNumElements) * 100.0 / - (static_cast(mMask) + 1))) - - if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { - // we have to resize, even though there would still be plenty of space left! - // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case - // we have to rehash a few times - nextHashMultiplier(); - rehashPowerOfTwo(mMask + 1, true); - } else { - // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. - rehashPowerOfTwo((mMask + 1) * 2, false); - } - return true; - } - - void nextHashMultiplier() { - // adding an *even* number, so that the multiplier will always stay odd. This is necessary - // so that the hash stays a mixing function (and thus doesn't have any information loss). - mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); - } - - void destroy() { - if (0 == mMask) { - // don't deallocate! - return; - } - - Destroyer::value>{} - .nodesDoNotDeallocate(*this); - - // This protection against not deleting mMask shouldn't be needed as it's sufficiently - // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise - // reports a compile error: attempt to free a non-heap object 'fm' - // [-Werror=free-nonheap-object] - if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - } - - void init() noexcept { - mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); - mInfo = reinterpret_cast(&mMask); - mNumElements = 0; - mMask = 0; - mMaxNumElementsAllowed = 0; - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // members are sorted so no padding occurs - uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 - Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 - uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 - size_t mNumElements = 0; // 8 byte 32 - size_t mMask = 0; // 8 byte 40 - size_t mMaxNumElementsAllowed = 0; // 8 byte 48 - InfoType mInfoInc = InitialInfoInc; // 4 byte 52 - InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 - // 16 byte 56 if NodeAllocator -}; - -} // namespace detail - -// map - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_flat_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_node_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_map = - detail::Table) <= sizeof(size_t) * 6 && - std::is_nothrow_move_constructible>::value && - std::is_nothrow_move_assignable>::value, - MaxLoadFactor100, Key, T, Hash, KeyEqual>; - -// set - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_flat_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_node_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_set = detail::Table::value && - std::is_nothrow_move_assignable::value, - MaxLoadFactor100, Key, void, Hash, KeyEqual>; - -} // namespace robin_hood - -#endif From 8b81c4a4ecfd7a3e56960befc2b3726f9dddd92d Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 03:28:35 +0100 Subject: [PATCH 27/91] Return const refs --- src/btop_draw.cpp | 26 +++++++++++++------------- src/btop_tools.hpp | 8 ++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 6f790eb..73dd1c0 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -800,7 +800,7 @@ namespace Cpu { const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1 or b_columns > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color - + safeVal(temp_graphs, 0)(safeVal(cpu.temp, 0), data_same or redraw); + + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; @@ -815,7 +815,7 @@ namespace Cpu { + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + safeVal(core_graphs, n)(safeVal(cpu.core_percent, n), data_same or redraw); + + core_graphs.at(n)(safeVal(cpu.core_percent, n), data_same or redraw); out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; @@ -825,7 +825,7 @@ namespace Cpu { const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) - + safeVal(temp_graphs, n+1)(safeVal(cpu.temp, n+1), data_same or redraw); + + temp_graphs.at(n+1)(safeVal(cpu.temp, n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -1242,7 +1242,7 @@ namespace Mem { if (title.empty()) title = capitalize(name); const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? safeVal(mem_graphs, name)(safeVal(mem.percent, name), redraw or data_same) : safeVal(mem_meters, name)(safeVal(mem.percent, name).back())); + const string graphics = (use_graphs ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same) : mem_meters.at(name)(safeVal(mem.percent, name).back())); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) @@ -1279,14 +1279,14 @@ namespace Mem { out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) - + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) + (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW"); if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount)({comb_val}, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized; cy += disks_io_h; } @@ -1294,8 +1294,8 @@ namespace Mem { const string human_read = (disk.io_read.back() > 0 ? "▲" + floating_humanizer(disk.io_read.back(), true) : "R"); const string human_write = (disk.io_write.back() > 0 ? "▼" + floating_humanizer(disk.io_write.back(), true) : "W"); if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' '); - out += Mv::to(y+1+cy, x+1+cx) + safeVal(io_graphs, mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) - + Mv::d(1) + safeVal(io_graphs, mount + "_write")(disk.io_write, redraw or data_same) + out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) + + Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write; cy += disks_io_h; } @@ -1320,18 +1320,18 @@ namespace Mem { if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) - + Mv::l(disks_width - 6) + safeVal(io_graphs, mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' - + safeVal(disk_meters_used, mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); + + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' - + safeVal(disk_meters_free, mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); + + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } @@ -1420,7 +1420,7 @@ namespace Net { //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { - out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + safeVal(graphs, dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); @@ -1891,7 +1891,7 @@ namespace Proc { + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") - + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + safeVal(p_graphs, p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; } diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index c7b2cc3..bda89f2 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -339,7 +339,7 @@ namespace Tools { template #ifdef BTOP_DEBUG - T safeVal(const std::unordered_map& map, const K& key, T fallback = T(), std::source_location loc = std::source_location::current()) { + const T& safeVal(const std::unordered_map& map, const K& key, const T& fallback = T{}, std::source_location loc = std::source_location::current()) { if (map.contains(key)) { return map.at(key); } else { @@ -348,7 +348,7 @@ namespace Tools { } }; #else - T safeVal(const std::unordered_map& map, const K& key, T fallback = T()) { + const T& safeVal(const std::unordered_map& map, const K& key, const T& fallback = T{}) { if (map.contains(key)) { return map.at(key); } else { @@ -360,7 +360,7 @@ namespace Tools { template #ifdef BTOP_DEBUG - T safeVal(const std::vector& vec, const size_t& index, T fallback = T(), std::source_location loc = std::source_location::current()) { + const T& safeVal(const std::vector& vec, const size_t& index, const T& fallback = T{}, std::source_location loc = std::source_location::current()) { if (index < vec.size()) { return vec.at(index); } else { @@ -369,7 +369,7 @@ namespace Tools { } }; #else - T safeVal(const std::vector& vec, const size_t& index, T fallback = T()) { + const T& safeVal(const std::vector& vec, const size_t& index, const T& fallback = T{}) { if (index < vec.size()) { return vec.at(index); } else { From 3c04a7a380a5a27630193e8805ef5f9d249cecb3 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 10:41:15 +0100 Subject: [PATCH 28/91] Added more checks and debug logging --- src/btop_draw.cpp | 16 ++++++++++++---- src/freebsd/btop_collect.cpp | 11 ++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 73dd1c0..840633b 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -798,7 +798,7 @@ namespace Cpu { if (show_temps) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); - if (b_column_size > 1 or b_columns > 1) + if (b_column_size > 1 or b_columns > 1 and temp_graphs.size() >= 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; @@ -811,6 +811,7 @@ namespace Cpu { int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); if (Shared::coreCount >= 100) core_width++; for (const auto& n : iota(0, Shared::coreCount)) { + if (core_graphs.size() < n+1) break; out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) @@ -820,7 +821,7 @@ namespace Cpu { out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; - if (show_temps and not hide_cores) { + if (show_temps and not hide_cores and temp_graphs.size() >= n) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) @@ -1242,7 +1243,10 @@ namespace Mem { if (title.empty()) title = capitalize(name); const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same) : mem_meters.at(name)(safeVal(mem.percent, name).back())); + const string graphics = ( + use_graphs and mem_graphs.contains(name) ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same) + : mem_meters.contains(name) ? mem_meters.at(name)(safeVal(mem.percent, name).back()) + : ""); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) @@ -1278,9 +1282,12 @@ namespace Mem { const string used_percent = to_string(disk.used_percent); out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } + if (io_graphs.contains(mount + "_activity")) { out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + } if (++cy > height - 3) break; + if (not io_graphs.contains(mount)) continue; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) @@ -1306,6 +1313,7 @@ namespace Mem { if (not disks.contains(mount)) continue; if (cy > height - 3) break; const auto& disk = safeVal(disks, mount); + if (disk.name.empty() or not disk_meters_used.contains(mount)) continue; auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); @@ -1329,7 +1337,7 @@ namespace Mem { + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; - if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { + if (disk_meters_free.contains(mount) and cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 2a7a7f3..8f0eee9 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -170,17 +170,23 @@ namespace Shared { Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Logger::debug("Init -> Cpu::collect()"); Cpu::collect(); for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); } + Logger::debug("Init -> Cpu::get_cpuName()"); Cpu::cpuName = Cpu::get_cpuName(); + Logger::debug("Init -> Cpu::get_sensors()"); Cpu::got_sensors = Cpu::get_sensors(); + Logger::debug("Init -> Cpu::get_core_mapping()"); Cpu::core_mapping = Cpu::get_core_mapping(); //? Init for namespace Mem Mem::old_uptime = system_uptime(); + Logger::debug("Init -> Mem::collect()"); Mem::collect(); + Logger::debug("Init -> Mem::get_zpools()"); Mem::get_zpools(); } @@ -576,13 +582,12 @@ namespace Mem { if (disk.dev.string().rfind(devStatName, 0) == 0) { devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE); assign_values(disk, total_bytes_read, total_bytes_write); - string mountpoint = mapping.at(disk.dev); + string mountpoint = safeVal(mapping, disk.dev); Logger::debug("dev " + devStatName + " -> " + mountpoint + " read=" + std::to_string(total_bytes_read) + " write=" + std::to_string(total_bytes_write)); } } } - Logger::debug(""); } // this code is for ZFS mounts @@ -604,7 +609,7 @@ namespace Mem { // alternatively you could parse the objset-0x... when this changes, you have a new entry string datasetname = string(value);// this is the zfs volume, like 'zroot/usr/home' -> this maps onto the device we get back from getmntinfo(3) if (mapping.contains(datasetname)) { - string mountpoint = mapping.at(datasetname); + string mountpoint = safeVal(mapping, datasetname); if (disks.contains(mountpoint)) { auto& disk = disks.at(mountpoint); assign_values(disk, nread, nwritten); From b4eb397fc69696db18c6f2b90b79172882c39128 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 25 Dec 2023 10:52:52 +0100 Subject: [PATCH 29/91] Fix errors --- src/btop_draw.cpp | 6 +++--- src/freebsd/btop_collect.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 840633b..97195f1 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -798,7 +798,7 @@ namespace Cpu { if (show_temps) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); - if (b_column_size > 1 or b_columns > 1 and temp_graphs.size() >= 1) + if ((b_column_size > 1 or b_columns > 1) and temp_graphs.size() >= 1ll) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; @@ -811,7 +811,7 @@ namespace Cpu { int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); if (Shared::coreCount >= 100) core_width++; for (const auto& n : iota(0, Shared::coreCount)) { - if (core_graphs.size() < n+1) break; + if (cmp_less(core_graphs.size(), n+1)) break; out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) @@ -821,7 +821,7 @@ namespace Cpu { out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; - if (show_temps and not hide_cores and temp_graphs.size() >= n) { + if (show_temps and not hide_cores and std::cmp_greater_equal(temp_graphs.size(), n)) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 8f0eee9..2db49a2 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -579,10 +579,10 @@ namespace Mem { auto d = cur.dinfo->devices[i]; string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number); for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1 - if (disk.dev.string().rfind(devStatName, 0) == 0) { + if (disk.dev.string().rfind(devStatName, 0) == 0 and mapping.contains(disk.dev)) { devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE); assign_values(disk, total_bytes_read, total_bytes_write); - string mountpoint = safeVal(mapping, disk.dev); + string mountpoint = mapping.at(disk.dev); Logger::debug("dev " + devStatName + " -> " + mountpoint + " read=" + std::to_string(total_bytes_read) + " write=" + std::to_string(total_bytes_write)); } } @@ -609,7 +609,7 @@ namespace Mem { // alternatively you could parse the objset-0x... when this changes, you have a new entry string datasetname = string(value);// this is the zfs volume, like 'zroot/usr/home' -> this maps onto the device we get back from getmntinfo(3) if (mapping.contains(datasetname)) { - string mountpoint = safeVal(mapping, datasetname); + string mountpoint = mapping.at(datasetname); if (disks.contains(mountpoint)) { auto& disk = disks.at(mountpoint); assign_values(disk, nread, nwritten); From 2796af3f37f07ba838d2eeb06e3d798f5ab00e55 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 26 Dec 2023 19:18:37 +0100 Subject: [PATCH 30/91] Document DEBUG flag for Makefile --- README.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 47f8f08..f220e86 100644 --- a/README.md +++ b/README.md @@ -364,11 +364,11 @@ Also needs a UTF8 locale and a font that covers: ### With Make -1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** +1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** ```bash sudo apt install coreutils sed git build-essential gcc-11 g++-11 - ``` + ``` 2. **Clone repository** @@ -391,17 +391,18 @@ Also needs a UTF8 locale and a font that covers: | `STATIC=true` | For static compilation | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | | `GPU_SUPPORT=` | Enable/disable GPU support (Enabled by default on X86_64 Linux) | | `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manualy set which compiler to use | Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc. -4. **Install** +4. **Install** ```bash sudo make install @@ -411,7 +412,7 @@ Also needs a UTF8 locale and a font that covers: Notice! Only use "sudo" when installing to a NON user owned directory. -5. **(Optional) Set suid bit to make btop always run as root (or other user)** +5. **(Optional) Set suid bit to make btop always run as root (or other user)** ```bash sudo make setuid @@ -561,13 +562,14 @@ Also needs a UTF8 locale and a font that covers: | `STATIC=true` | For static compilation (only libgcc and libstdc++) | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | - | `ARCH=` | To manually set the target architecture | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manualy set which compiler to use | - Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. -4. **Install** +4. **Install** ```bash sudo gmake install @@ -577,7 +579,7 @@ Also needs a UTF8 locale and a font that covers: Notice! Only use "sudo" when installing to a NON user owned directory. -5. **(Recommended) Set suid bit to make btop always run as root (or other user)** +5. **(Recommended) Set suid bit to make btop always run as root (or other user)** ```bash sudo gmake setuid @@ -726,18 +728,19 @@ Also needs a UTF8 locale and a font that covers: | `STATIC=true` | For static compilation (only libgcc and libstdc++) | | `QUIET=true` | For less verbose output | | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | - | `ARCH=` | To manually set the target architecture | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manualy set which compiler to use | - Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. 4. **Install** ```bash sudo gmake install ``` - + Append `PREFIX=/target/dir` to set target, default: `/usr/local` Notice! Only use "sudo" when installing to a NON user owned directory. @@ -747,7 +750,7 @@ Also needs a UTF8 locale and a font that covers: ```bash sudo gmake setuid ``` - + No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. Run after make install and use same PREFIX if any was used at install. From ad14554f32cc9c19f3cdc8e28ea6012a974a533a Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 26 Dec 2023 19:32:43 +0100 Subject: [PATCH 31/91] Try alternative names for GPU libraries during GPU init --- src/linux/btop_collect.cpp | 43 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 1a8dbb4..c39e964 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -955,10 +955,27 @@ namespace Gpu { return false; } + //? Try possible library names for libnvidia-ml.so + const array libNvAlts = { + "libnvidia-ml.so", + "libnvidia-ml.so.1", + }; + + for (const auto& l : libNvAlts) { + nvml_dl_handle = dlopen(l, RTLD_LAZY); + if (nvml_dl_handle != nullptr) { + break; + } + } + if (!nvml_dl_handle) { + Logger::info("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: "s + dlerror()); + return false; + } + auto load_nvml_sym = [&](const char sym_name[]) { auto sym = dlsym(nvml_dl_handle, sym_name); auto err = dlerror(); - if (err != NULL) { + if (err != nullptr) { Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err); return (void*)nullptr; } else return sym; @@ -1214,19 +1231,31 @@ namespace Gpu { //? Dynamic loading & linking #if !defined(RSMI_STATIC) - rsmi_dl_handle = dlopen("/opt/rocm/lib/librocm_smi64.so", RTLD_LAZY); // first try /lib and /usr/lib, then /opt/rocm/lib if that fails - if (dlerror() != NULL) { - rsmi_dl_handle = dlopen("librocm_smi64.so", RTLD_LAZY); - } + + //? Try possible library paths and names for librocm_smi64.so + const array libRocAlts = { + "/opt/rocm/lib/librocm_smi64.so", + "librocm_smi64.so", + "librocm_smi64.so.5", // fedora + "librocm_smi64.so.1.0", // debian + }; + + for (const auto& l : libRocAlts) { + rsmi_dl_handle = dlopen(l, RTLD_LAZY); + if (rsmi_dl_handle != nullptr) { + break; + } + } + if (!rsmi_dl_handle) { - Logger::debug(std::string("Failed to load librocm_smi64.so, AMD GPUs will not be detected: ") + dlerror()); + Logger::info("Failed to load librocm_smi64.so, AMD GPUs will not be detected: "s + dlerror()); return false; } auto load_rsmi_sym = [&](const char sym_name[]) { auto sym = dlsym(rsmi_dl_handle, sym_name); auto err = dlerror(); - if (err != NULL) { + if (err != nullptr) { Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err); return (void*)nullptr; } else return sym; From 2fba934cde14d72e3ed58c389dc2e24d7297475b Mon Sep 17 00:00:00 2001 From: aristocratos Date: Wed, 27 Dec 2023 00:54:28 +0100 Subject: [PATCH 32/91] Fixed leftover code in GPU init logging false errors --- src/linux/btop_collect.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index c39e964..677c3fe 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -949,12 +949,6 @@ namespace Gpu { if (initialized) return false; //? Dynamic loading & linking - nvml_dl_handle = dlopen("libnvidia-ml.so", RTLD_LAZY); - if (!nvml_dl_handle) { - Logger::info(std::string("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: ") + dlerror()); - return false; - } - //? Try possible library names for libnvidia-ml.so const array libNvAlts = { "libnvidia-ml.so", From 285fb215d12a5e0c686b29e1039027cbb2b246da Mon Sep 17 00:00:00 2001 From: aristocratos Date: Thu, 28 Dec 2023 13:10:18 +0100 Subject: [PATCH 33/91] Proc::draw() -> Use std::erase_if() instead of for loops --- src/btop_draw.cpp | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 97195f1..0ceaa2f 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -1799,7 +1799,7 @@ namespace Proc { p_counters[p.pid] = 0; } else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) { - p_graphs.erase(p.pid); + if (p_graphs.contains(p.pid)) p_graphs.erase(p.pid); p_counters.erase(p.pid); } else @@ -1927,22 +1927,18 @@ namespace Proc { //? Clear out left over graphs from dead processes at a regular interval if (not data_same and ++counter >= 100) { counter = 0; - for (auto element = p_graphs.begin(); element != p_graphs.end();) { - if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { - element = p_graphs.erase(element); - p_counters.erase(element->first); - } - else - ++element; - } - for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { - if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { - element = p_wide_cmd.erase(element); - } - else - ++element; - } + std::erase_if(p_graphs, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + std::erase_if(p_counters, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + std::erase_if(p_wide_cmd, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); } if (selected == 0 and selected_pid != 0) { From 934a9e3cf28114afe5e5578ed6d241481310f5f1 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 15:29:53 +0100 Subject: [PATCH 34/91] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 285fb215d12a5e0c686b29e1039027cbb2b246da Author: aristocratos Date: Thu Dec 28 13:10:18 2023 +0100 Proc::draw() -> Use std::erase_if() instead of for loops commit 2fba934cde14d72e3ed58c389dc2e24d7297475b Author: aristocratos Date: Wed Dec 27 00:54:28 2023 +0100 Fixed leftover code in GPU init logging false errors commit ad14554f32cc9c19f3cdc8e28ea6012a974a533a Author: aristocratos Date: Tue Dec 26 19:32:43 2023 +0100 Try alternative names for GPU libraries during GPU init commit a8fda16bf6ead94bc5ffafa3e622ee60b1d92d7b Merge: e15e0b7 2796af3 Author: Jakob P. Liljenberg Date: Tue Dec 26 19:19:14 2023 +0100 Merge pull request #696 from aristocratos/map_safety commit 2796af3f37f07ba838d2eeb06e3d798f5ab00e55 Author: aristocratos Date: Tue Dec 26 19:18:37 2023 +0100 Document DEBUG flag for Makefile commit f484326bf20a8deb4ac942aec2b5d9173d58ad4b Merge: b4eb397 e15e0b7 Author: aristocratos Date: Tue Dec 26 19:11:26 2023 +0100 Merge branch 'main' into map_safety commit b4eb397fc69696db18c6f2b90b79172882c39128 Author: aristocratos Date: Mon Dec 25 10:52:52 2023 +0100 Fix errors commit 3c04a7a380a5a27630193e8805ef5f9d249cecb3 Author: aristocratos Date: Mon Dec 25 10:41:15 2023 +0100 Added more checks and debug logging commit 8b81c4a4ecfd7a3e56960befc2b3726f9dddd92d Author: aristocratos Date: Mon Dec 25 03:28:35 2023 +0100 Return const refs commit f836233b64aeee1513be9dc74c4dc60b6248e578 Author: aristocratos Date: Mon Dec 25 02:49:24 2023 +0100 Remove robin_hood.h commit 3a8ceacaf89e7443014613dbc888b8b1657832fe Author: aristocratos Date: Mon Dec 25 02:37:32 2023 +0100 Fix call to compact and missing utility include commit e15e0b71881641ea6d6751e3acd46631d91b494b Author: aristocratos Date: Mon Dec 25 02:27:38 2023 +0100 Revert "Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback" This reverts commit 6c87ab61969f028e49ad596139cff53eb6c3becc. commit ced47a960f649d3c4e81c91128fe97a81e4c4ad5 Author: aristocratos Date: Mon Dec 25 02:26:13 2023 +0100 Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback commit 6c87ab61969f028e49ad596139cff53eb6c3becc Author: aristocratos Date: Mon Dec 25 02:16:15 2023 +0100 Replace robin_hood map and set with STD alternative and add safeVal() function for map/vector access with fallback commit a2325371d432f62b362fe1a9531a79c79fc56baa Merge: aab2e8c b598f02 Author: Jakob P. Liljenberg Date: Sun Dec 17 19:56:31 2023 +0100 Merge pull request #690 from aristocratos/osx-fix commit b598f02468480a54fe1e283d02db5228969f7522 Merge: b1fe377 aab2e8c Author: Jakob P. Liljenberg Date: Sun Dec 17 12:06:39 2023 +0100 Merge branch 'main' into osx-fix commit aab2e8cc552c752cbb15675c8ab96e1bd785ad3b Author: Jakob P. Liljenberg Date: Sun Dec 17 12:03:47 2023 +0100 Fixed test-snap-can-build.yml commit b1fe3779e1b18c9087cbf399afbcfb3622cebd12 Merge: 7805242 2d15c41 Author: Jakob P. Liljenberg Date: Sun Dec 17 11:56:14 2023 +0100 Merge branch 'main' into osx-fix commit 2d15c41555024dba0c1e0af91220fa2f8406b9f4 Merge: fe699b4 2d3e453 Author: Jakob P. Liljenberg Date: Sun Dec 17 11:54:49 2023 +0100 Merge pull request #684 from kz6fittycent/main commit 2d3e453ed527f2d045b9e615422f708820438298 Merge: 0a38864 fe699b4 Author: kz6fittycent Date: Fri Dec 15 12:02:11 2023 -0600 Merge branch 'main' into main commit 0a388647ccbdaa68a8939a8df806afee29b63489 Author: kz6fittycent Date: Fri Dec 15 12:01:45 2023 -0600 Update test-snap-can-build.yml whoops commit 49f425f35657d9f448e112968a2405421bd1b378 Author: kz6fittycent Date: Fri Dec 15 12:00:48 2023 -0600 Update test-snap-can-build.yml https://github.com/aristocratos/btop/pull/684#issuecomment-1852801811 commit 780524267fe16b11638ca002d636c5feb8cbd5b2 Author: Jos Dehaes Date: Fri Dec 15 09:02:57 2023 +0100 conditional compile on Big Sur and up commit fe699b433398818cf2145d2ca4d2e364a089a4d2 Author: aristocratos Date: Tue Dec 12 23:20:09 2023 +0100 Version bump to 1.3.0 in preparation for upcoming release commit 2d2df23198b6b08bdce21ee0d4f7e3a2b6c37d0b Merge: d7b581e b71538e Author: aristocratos Date: Tue Dec 12 23:19:31 2023 +0100 Merge branch 'main' of github.com:aristocratos/btop commit d7b581eda4560e969459d5f4a1e0ef811e5a08be Author: aristocratos Date: Tue Dec 12 23:17:36 2023 +0100 Updated changes commit b71538eabe06607042201e21af9a078277f3b3e8 Merge: a017056 730af5d Author: Jakob P. Liljenberg Date: Tue Dec 12 23:07:39 2023 +0100 Merge pull request #666 from muneebmahmed/macos-clang commit 730af5d4e1a09996f9e83c6aa26382a11964b03f Merge: 0246b1b a017056 Author: Jakob P. Liljenberg Date: Tue Dec 12 23:05:52 2023 +0100 Merge branch 'main' into macos-clang commit a017056ea07f6532b333f3e6f10a023505fb0470 Author: aristocratos Date: Tue Dec 12 23:05:07 2023 +0100 Added swap to ignore for statvfs() since it will always fail commit e770cccaf82cb75fe1e61c227284929e5a4acde1 Author: aristocratos Date: Tue Dec 12 22:55:48 2023 +0100 Added try->catch for get_zfs_stat_file() to avoid fs error commit 0246b1b971a3f991540acd72ef66b5481d9bc76f Author: Muneeb Ahmed <32603485+muneebmahmed@users.noreply.github.com> Date: Mon Nov 20 12:18:40 2023 -0800 Enable macos clang Apple clang uses different versioning from LLVM, so 15.0.0 is compatible commit 6282f36f8fe2fc1fa95a5858c044fbc6c7d965cf Merge: cfd20a3 be73160 Author: Jakob P. Liljenberg Date: Tue Dec 12 22:06:02 2023 +0100 Merge pull request #675 from imwints/cmake commit be731600f1ec6cb58edc6947d41d2709c6d33635 Merge: f4b14ce cfd20a3 Author: Jakob P. Liljenberg Date: Tue Dec 12 22:01:21 2023 +0100 Merge branch 'main' into cmake commit 450b59b657618beb7a88f34072210e27cab43ca5 Merge: 875f08b cfd20a3 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:55:27 2023 +0100 Merge branch 'main' into main commit cfd20a374b11b75d73e043979d0b6dd4314d2919 Merge: 14e664e b6a8696 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:48:55 2023 +0100 Merge pull request #677 from imwints/cpu-model commit b6a86962e23422b6f09ea40347cd4a7a49ae6647 Merge: 8096433 14e664e Author: Jakob P. Liljenberg Date: Tue Dec 12 21:47:01 2023 +0100 Merge branch 'main' into cpu-model commit 14e664e7564f595d911e462414357caade91eede Merge: 0d35746 5902484 Author: Jakob P. Liljenberg Date: Tue Dec 12 21:41:55 2023 +0100 Merge pull request #679 from masiboss/main commit 875f08ba5ea72327573235a265f13c0441ee67e5 Author: kz6fittycent Date: Tue Dec 12 14:27:16 2023 -0600 Update snapcraft.yaml - opengl - Testing opengl plug commit 3ee4b18e57bc9dfe3124ad615bc68aab1772c283 Author: kz6fittycent Date: Tue Dec 12 14:14:59 2023 -0600 Update bug_report.md - added snap info for bug reports to delineate commit 2973a76f2b3183b571e6706e523aa42f6b1b7f69 Merge: fb782a2 0d35746 Author: kz6fittycent Date: Tue Dec 12 14:06:44 2023 -0600 Merge branch 'aristocratos:main' into main commit fb782a2ab32cc60ff6bcaec5ae13514ce5ca5390 Author: kz6fittycent Date: Tue Dec 12 14:02:27 2023 -0600 Create test-snap-can-build.yml commit 5902484f39fea0751093368e3181ac8cbe37e9a6 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 21:42:11 2023 +0100 simplify removal of "Apple" commit 5beb9e12e5645e64644eb8465b097468172e244f Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 20:56:40 2023 +0100 in case apple decides to add another suffix to the cpu name commit 1b2f11b41234f7cdf83212c81cdbf086f491049d Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 20:49:34 2023 +0100 cut less of cpu name if frequency is not shown commit bcf4ad8ab6a8b69f17ad7936a36595f15dbc0953 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:50:12 2023 +0100 fix array out of bounds on regular m chip commit aeefcacbc9c48aa5fad658b07f046d20a0b863c7 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:34:11 2023 +0100 fix cpu version not included commit 23698940df1b387d131a250f754f3f47b8480833 Author: masiboss <32394683+masiboss@users.noreply.github.com> Date: Thu Dec 7 19:23:58 2023 +0100 strip "Apple" from name of Apple silicon chips commit 809643373648c510788706404d5ec00ba714baba Author: Steffen Winter Date: Tue Dec 5 02:34:24 2023 +0100 Fix printed model name for older Intel CPU commit f4b14ce97e35d439bcc3207c3eb78da442c92fa9 Author: Steffen Winter Date: Tue Dec 5 01:00:14 2023 +0100 Add CMake compile instructions for macOS commit 97b35d97206417a5170324f7d98b815bec002b36 Author: Steffen Winter Date: Sat Dec 2 00:35:13 2023 +0100 Add cmake workflow for all platforms commit e35538fa29e7975a6ca92bb2c0fd0e36d8108ba5 Author: Steffen Winter Date: Sat Dec 2 00:34:32 2023 +0100 Patch RPATH on FreeBSD, support OSX and format commit 0d357468b50a13b48868b4969b4f19000c90d5d6 Merge: ebc46ca 00f58b6 Author: Jakob P. Liljenberg Date: Fri Dec 1 22:52:51 2023 +0100 Merge pull request #674 from imwints/bsd-workflow Provide FreeBSD static release binaries commit 00f58b6228c1ea99ab32d0ec48c3d4aa93611614 Author: Steffen Winter Date: Thu Nov 30 23:07:52 2023 +0100 Provide FreeBSD static release binaries Bumps vmaction@freesdb-vm to version 1 which runs on Linux and doesn't hang all the time. Also uses clang for full static compilation commit ebc46ca12cc0ee0172ac44ad6f9bdd30c72e3685 Author: Jakob P. Liljenberg Date: Mon Nov 27 18:21:42 2023 +0100 Clean up compile instructions commit d1384c9341e73a5289a4d754fdcce847c4dca736 Merge: 2b0cc37 6f12e35 Author: Jakob P. Liljenberg Date: Mon Nov 27 17:53:39 2023 +0100 Merge pull request #671 from imwints/cmake-gpu Bring GPU support to CMake and improve how Make handles the ROCm library build commit 6f12e3555d9558266e5ffc7f6847f0a7273db5b8 Author: Steffen Winter Date: Mon Nov 27 00:33:11 2023 +0100 Properly invoke CMake to build ROCm * Build an optimized library by default * Only build the library target * ROCm is build with debug symbols when `make DEBUG=true` * Enable LTO * Use the more generic CMake build command instead of calling make directly, this always uses all cores by default and makes it easier to switch to another generator e.g. Ninja * Use a variable to store the ROCm source directory. The directory can be changed with `make ROCM_DIR=` * The static library is now directly linked by CMake and not created off of the object files from a shared library build * The C++ compiler used to compile btop is now used to compile ROCm to avoid name mangling when `CXX` from the environment and `make CXX=` differ * CMake is invoked from btop's root directory commit 0585bc9cfbcb023ad10b57cbf52cc90623e6d307 Author: Steffen Winter Date: Mon Nov 27 00:31:10 2023 +0100 Suppress all output from ROCm build Similar to including external include files with `-isystem`, ignore output from ROCm build since these warnings aren't a concern here commit 831be262b0a88090bde36eee0dcac8e8abb7569b Author: Steffen Winter Date: Mon Nov 27 00:29:14 2023 +0100 Remove ROCm object files with `make clean/distclean` commit 2f59e61d875b86cb99d9d24819dea607239ad517 Author: Steffen Winter Date: Sun Nov 26 22:56:58 2023 +0100 Add GPU options for cmake based builds commit 7588d96dd4e0fdbba4943bddd7709004876aa7ab Author: Steffen Winter Date: Sun Nov 26 21:40:29 2023 +0100 Add check for header commit ebbb769a6aaf0a6245111eb55f239d5c45be0cb9 Author: Steffen Winter Date: Sun Nov 26 21:39:13 2023 +0100 Move calls to find_package to where they're required commit ed0fa34a9d628b21a1abed6034ab866c4db72445 Author: Steffen Winter Date: Sun Nov 26 19:39:29 2023 +0100 Bump required CMake version commit 2b0cc3763271a9aae5242dbb7174bfec5c5fdae5 Author: Jakob P. Liljenberg Date: Sat Nov 25 23:11:54 2023 +0100 Update compile instructions for Gpu support commit 359c67136b75efe80bb1d2471d0e87ab5ed5b20c Author: aristocratos Date: Sat Nov 25 22:49:26 2023 +0100 Update changelog commit 5b01235315034f9c75880d0d08ab085b4a804751 Merge: 0267eba 0bb8599 Author: Jakob P. Liljenberg Date: Sat Nov 25 21:57:32 2023 +0100 Merge pull request #529 from romner-set/main Add GPU monitoring support commit 0bb8599a967c92a2767bcf7f488d5e1acfabeef8 Merge: 94d4502 0267eba Author: Jakob P. Liljenberg Date: Sat Nov 25 21:51:09 2023 +0100 Merge branch 'main' into main commit 94d4502901124ff20146a0b6f5c30efe5b4d2a32 Author: aristocratos Date: Sat Nov 25 21:48:50 2023 +0100 Readme update and Makfile fixes. commit 19bcff894bd8f018bc3c76607b1dd91200537923 Author: aristocratos Date: Sat Nov 25 21:01:11 2023 +0100 Squashed commit of the following: commit 0267eba2bbebd6f166b24358159d7fb094fae052 Merge: 50bbab0 e81cf2b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:43:18 2023 +0100 Merge pull request #659 from ivanp7/patch-1 Add alternative key codes for Delete, Insert, Home, End commit 50bbab05122b2f8c35c90e4a6e01d5c52e5eca60 Merge: 9edbf27 5a14c7b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:35:50 2023 +0100 Merge pull request #660 from stradicat/feature/elementarish Elementarish theme: color update according to Elementary palette commit 5a14c7b6fa41a7fe7061c0aa2f2d0c0fe530e495 Merge: 979506f 71eb414 Author: Dennis Mayr Date: Wed Nov 15 17:27:34 2023 -0300 Merge branch 'main' of https://github.com/stradicat/btop commit 979506f18ecdc1475b882d7dadc220386169b7db Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit 71eb4142e8204303af091555067b93d82e5dcec1 Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit e81cf2b7ff4111e279e1111127e49c6858d83d5e Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Tue Nov 7 15:12:27 2023 +0000 Add alternative key codes for Insert, Home, End commit f9452ff6d56af721fb5526ede11206c7d3b885b8 Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Mon Nov 6 13:31:53 2023 +0000 Add alternative Delete key code Delete key not always produces ^[[3~, on some terminals (like st) it produces ^[[P. commit 9edbf27f1b6d5844a97325fcda732762ba086a99 Merge: 2a864f6 ff1f51c Author: Jakob P. Liljenberg Date: Sat Oct 21 02:09:55 2023 +0200 Merge pull request #649 from nobounce/workflow-timeout Set FreeBSD workflow timeout commit ff1f51ccbb6d6133292e57ba1b8edb8b6c941fc9 Author: Steffen Winter Date: Wed Oct 18 22:26:36 2023 +0200 Set FreeBSD workflow timeout Recently the FreeBSD workflow has started to hang in a boot loop when the VM starts up. The issue is being tracked upstream but there is not response at the moment. To work around this set a timeout to not waste CI minutes. Other workflows might also want this change since they don't take 20 minutes anyway. commit 2a864f6f2ea60df16b3f015885eb3c18a48b9b78 Merge: 636eb25 b2bf8ef Author: Jakob P. Liljenberg Date: Sat Oct 7 10:40:54 2023 +0200 Merge pull request #643 from DecklynKern/main Fix scrollbar not clearing sometimes. commit b2bf8ef504f29650f8fe0adab41c3cac35b67567 Author: DecklynKern Date: Fri Oct 6 17:33:38 2023 -0600 Fix scrollbar not clearing sometimes. commit 636eb25f5e31a7af337b024873b2ceb42650ebdb Merge: 260c0f6 b5ba2fc Author: Jakob P. Liljenberg Date: Sat Sep 30 19:51:03 2023 +0200 Merge pull request #623 from rahulaggarwal965/main Add keybind for toggling memory display mode in PROC box commit b5ba2fc9635e540142d3dd3eccd866865c9393fd Author: Rahul Aggarwal Date: Wed Sep 20 22:55:56 2023 -0400 Add keybind for toggling memory display mode in PROC box commit 260c0f662313fe0d0df859645f5731af04fc9fa7 Merge: 52bfff7 e6a06eb Author: Jakob P. Liljenberg Date: Sat Sep 30 18:56:25 2023 +0200 Merge pull request #635 from lvxnull/editorconfig Add hpp files to .editorconfig commit e6a06eb729f11fb8b14f104a041c8504772a8c95 Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Thu Sep 28 19:44:47 2023 +0200 Add hpp files to .editorconfig commit 52bfff7ceb3d259b1a71002fcbfb20261294bdd7 Merge: 1f72e56 19dbbe1 Author: Jakob P. Liljenberg Date: Sat Sep 30 18:55:08 2023 +0200 Merge pull request #636 from nobounce/performance-iili Minor string initialization improvement commit 19dbbe1a17f7e6453709c37a23859e5d73591e53 Author: nobounce Date: Fri Sep 29 12:20:59 2023 +0200 Minor string initialization improvement commit 1f72e56c7d6e70f8851134c0a28e17fb0a824a71 Merge: 278a0e6 cdcf8bc Author: Jakob P. Liljenberg Date: Fri Sep 29 10:43:21 2023 +0200 Merge pull request #633 from crestfallnatwork/main [fix] Made disks statvfs logic asynchronous. commit cdcf8bc92978c826d9c1768b547df3b7484003f2 Author: crestfalln Date: Fri Sep 29 09:07:27 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 9b4e85f08dc3be40d8f4904093cd2bdd096e60fa Author: crestfalln Date: Thu Sep 28 04:57:05 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 889623874ef6233610ed529bff18e1ba2c407e14 Author: crestfalln Date: Wed Sep 27 23:57:06 2023 +0530 made disks stat logic async commit 278a0e6b171a5f967e0680f679c20d9fc8d58c6f Merge: d16adc9 e89519f Author: Jakob P. Liljenberg Date: Thu Sep 28 18:32:09 2023 +0200 Merge pull request #630 from lvxnull/signal-list Fix signal list on non-linux/weird linux platforms commit e89519fbb2cd53ddb06ab0a39093c19fc595277c Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Sun Sep 24 21:44:38 2023 +0200 Fix signal list on non-linux/weird linux platforms commit d16adc9fd03322d46f1b84e5ebe7d426f726a5cc Merge: 2c3ac48 f34b408 Author: Jakob P. Liljenberg Date: Thu Sep 28 18:20:42 2023 +0200 Merge pull request #618 from nobounce/aggregate-child-processes Add option to accumulate a child's resources in parent in tree-view commit f34b40892fef31f657cbe8066c8b0d41ed37c0fc Author: nobounce Date: Sun Sep 24 16:34:50 2023 +0200 Make process thread count better readable when wider than 5 digits commit 6027cedd424e963bc6fe9017252ed4f1c9f8634b Author: nobounce Date: Thu Sep 14 23:27:05 2023 +0200 Add option to accumulate a child's resources in parent in tree-view commit 2c3ac4855de49563edd4ef199b0be74babc7ce32 Merge: f90dc37 5c6a281 Author: Jakob P. Liljenberg Date: Wed Sep 13 21:14:56 2023 +0200 Merge pull request #589 from nobounce/cmake Add CMake support for Linux commit f90dc37c26024f28c2a88d87d041fca1f1b5db1e Merge: 0cac861 68a49c1 Author: Jakob P. Liljenberg Date: Wed Sep 13 20:27:05 2023 +0200 Merge pull request #610 from SidVeld/feature/horizon-theme Horizon theme commit 5c6a2810021c352584a0834c95eff4ece7454c0e Author: nobounce Date: Tue Aug 29 20:39:00 2023 +0200 Add CMake support Linux is completly supported FreeBSD is not able to create a static executable for now. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273398 MacOS was not tested commit 68a49c10a63371b65beb670f7a55ccb43fe38311 Author: SidVeld Date: Wed Sep 6 18:03:31 2023 +0300 Add horizon theme commit 0cac8619105521999d603efaeb7d7eed8c38d746 Merge: 31be436 f798acd Author: Jakob P. Liljenberg Date: Tue Sep 5 19:27:38 2023 +0200 Merge pull request #609 from scorpion-26/byteconv Fix short conversion of 1000-1023 *iB commit f798acdaf71455684883df0ffb31d29293f788b2 Author: scorpion-26 Date: Tue Sep 5 18:00:47 2023 +0200 Fix short conversion of 1000-1023*iB floating_humanizer([1000-1024], true) with base 8 returns "2K", whereas it should return "1.0K" to align with other formats. The conversion is also broken for all other units(e.g. 1023M is also broken and returns "2G") commit 31be4362ce122182862ac338a9a479cde3a4c8e2 Author: aristocratos Date: Sun Aug 27 02:00:07 2023 +0200 FreeBSD Github action 13.1 -> 13.2 and static libgcc and libstdc++ commit fc523fd1d0ef49443367056e30653933efc8920c Author: aristocratos Date: Sun Aug 27 01:36:26 2023 +0200 Fix for FreeBSD github action not failing "correctly"... commit b87772611cb6290e834c6beb27eaceea46a10d90 Author: aristocratos Date: Sat Nov 25 20:44:45 2023 +0100 Added definition GPU_SUPPORT to toggle GPU related code commit 0267eba2bbebd6f166b24358159d7fb094fae052 Merge: 50bbab0 e81cf2b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:43:18 2023 +0100 Merge pull request #659 from ivanp7/patch-1 Add alternative key codes for Delete, Insert, Home, End commit 50bbab05122b2f8c35c90e4a6e01d5c52e5eca60 Merge: 9edbf27 5a14c7b Author: Jakob P. Liljenberg Date: Wed Nov 15 21:35:50 2023 +0100 Merge pull request #660 from stradicat/feature/elementarish Elementarish theme: color update according to Elementary palette commit 5a14c7b6fa41a7fe7061c0aa2f2d0c0fe530e495 Merge: 979506f 71eb414 Author: Dennis Mayr Date: Wed Nov 15 17:27:34 2023 -0300 Merge branch 'main' of https://github.com/stradicat/btop commit 979506f18ecdc1475b882d7dadc220386169b7db Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit 71eb4142e8204303af091555067b93d82e5dcec1 Author: Dennis Mayr Date: Wed Nov 8 11:17:47 2023 -0300 Elementarish theme: color update according to Elementary palette commit e81cf2b7ff4111e279e1111127e49c6858d83d5e Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Tue Nov 7 15:12:27 2023 +0000 Add alternative key codes for Insert, Home, End commit f9452ff6d56af721fb5526ede11206c7d3b885b8 Author: vân <3432246+ivanp7@users.noreply.github.com> Date: Mon Nov 6 13:31:53 2023 +0000 Add alternative Delete key code Delete key not always produces ^[[3~, on some terminals (like st) it produces ^[[P. commit 9edbf27f1b6d5844a97325fcda732762ba086a99 Merge: 2a864f6 ff1f51c Author: Jakob P. Liljenberg Date: Sat Oct 21 02:09:55 2023 +0200 Merge pull request #649 from nobounce/workflow-timeout Set FreeBSD workflow timeout commit ff1f51ccbb6d6133292e57ba1b8edb8b6c941fc9 Author: Steffen Winter Date: Wed Oct 18 22:26:36 2023 +0200 Set FreeBSD workflow timeout Recently the FreeBSD workflow has started to hang in a boot loop when the VM starts up. The issue is being tracked upstream but there is not response at the moment. To work around this set a timeout to not waste CI minutes. Other workflows might also want this change since they don't take 20 minutes anyway. commit 2a864f6f2ea60df16b3f015885eb3c18a48b9b78 Merge: 636eb25 b2bf8ef Author: Jakob P. Liljenberg Date: Sat Oct 7 10:40:54 2023 +0200 Merge pull request #643 from DecklynKern/main Fix scrollbar not clearing sometimes. commit b2bf8ef504f29650f8fe0adab41c3cac35b67567 Author: DecklynKern Date: Fri Oct 6 17:33:38 2023 -0600 Fix scrollbar not clearing sometimes. commit 636eb25f5e31a7af337b024873b2ceb42650ebdb Merge: 260c0f6 b5ba2fc Author: Jakob P. Liljenberg Date: Sat Sep 30 19:51:03 2023 +0200 Merge pull request #623 from rahulaggarwal965/main Add keybind for toggling memory display mode in PROC box commit b5ba2fc9635e540142d3dd3eccd866865c9393fd Author: Rahul Aggarwal Date: Wed Sep 20 22:55:56 2023 -0400 Add keybind for toggling memory display mode in PROC box commit 260c0f662313fe0d0df859645f5731af04fc9fa7 Merge: 52bfff7 e6a06eb Author: Jakob P. Liljenberg Date: Sat Sep 30 18:56:25 2023 +0200 Merge pull request #635 from lvxnull/editorconfig Add hpp files to .editorconfig commit e6a06eb729f11fb8b14f104a041c8504772a8c95 Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Thu Sep 28 19:44:47 2023 +0200 Add hpp files to .editorconfig commit 52bfff7ceb3d259b1a71002fcbfb20261294bdd7 Merge: 1f72e56 19dbbe1 Author: Jakob P. Liljenberg Date: Sat Sep 30 18:55:08 2023 +0200 Merge pull request #636 from nobounce/performance-iili Minor string initialization improvement commit 19dbbe1a17f7e6453709c37a23859e5d73591e53 Author: nobounce Date: Fri Sep 29 12:20:59 2023 +0200 Minor string initialization improvement commit 1f72e56c7d6e70f8851134c0a28e17fb0a824a71 Merge: 278a0e6 cdcf8bc Author: Jakob P. Liljenberg Date: Fri Sep 29 10:43:21 2023 +0200 Merge pull request #633 from crestfallnatwork/main [fix] Made disks statvfs logic asynchronous. commit cdcf8bc92978c826d9c1768b547df3b7484003f2 Author: crestfalln Date: Fri Sep 29 09:07:27 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 9b4e85f08dc3be40d8f4904093cd2bdd096e60fa Author: crestfalln Date: Thu Sep 28 04:57:05 2023 +0530 fixed bug where updated disks stats overrided disk io data commit 889623874ef6233610ed529bff18e1ba2c407e14 Author: crestfalln Date: Wed Sep 27 23:57:06 2023 +0530 made disks stat logic async commit 278a0e6b171a5f967e0680f679c20d9fc8d58c6f Merge: d16adc9 e89519f Author: Jakob P. Liljenberg Date: Thu Sep 28 18:32:09 2023 +0200 Merge pull request #630 from lvxnull/signal-list Fix signal list on non-linux/weird linux platforms commit e89519fbb2cd53ddb06ab0a39093c19fc595277c Author: lvxnull <86745229+lvxnull@users.noreply.github.com> Date: Sun Sep 24 21:44:38 2023 +0200 Fix signal list on non-linux/weird linux platforms commit d16adc9fd03322d46f1b84e5ebe7d426f726a5cc Merge: 2c3ac48 f34b408 Author: Jakob P. Liljenberg Date: Thu Sep 28 18:20:42 2023 +0200 Merge pull request #618 from nobounce/aggregate-child-processes Add option to accumulate a child's resources in parent in tree-view commit f34b40892fef31f657cbe8066c8b0d41ed37c0fc Author: nobounce Date: Sun Sep 24 16:34:50 2023 +0200 Make process thread count better readable when wider than 5 digits commit 6027cedd424e963bc6fe9017252ed4f1c9f8634b Author: nobounce Date: Thu Sep 14 23:27:05 2023 +0200 Add option to accumulate a child's resources in parent in tree-view commit 2c3ac4855de49563edd4ef199b0be74babc7ce32 Merge: f90dc37 5c6a281 Author: Jakob P. Liljenberg Date: Wed Sep 13 21:14:56 2023 +0200 Merge pull request #589 from nobounce/cmake Add CMake support for Linux commit f90dc37c26024f28c2a88d87d041fca1f1b5db1e Merge: 0cac861 68a49c1 Author: Jakob P. Liljenberg Date: Wed Sep 13 20:27:05 2023 +0200 Merge pull request #610 from SidVeld/feature/horizon-theme Horizon theme commit 5c6a2810021c352584a0834c95eff4ece7454c0e Author: nobounce Date: Tue Aug 29 20:39:00 2023 +0200 Add CMake support Linux is completly supported FreeBSD is not able to create a static executable for now. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273398 MacOS was not tested commit 68a49c10a63371b65beb670f7a55ccb43fe38311 Author: SidVeld Date: Wed Sep 6 18:03:31 2023 +0300 Add horizon theme commit 0cac8619105521999d603efaeb7d7eed8c38d746 Merge: 31be436 f798acd Author: Jakob P. Liljenberg Date: Tue Sep 5 19:27:38 2023 +0200 Merge pull request #609 from scorpion-26/byteconv Fix short conversion of 1000-1023 *iB commit f798acdaf71455684883df0ffb31d29293f788b2 Author: scorpion-26 Date: Tue Sep 5 18:00:47 2023 +0200 Fix short conversion of 1000-1023*iB floating_humanizer([1000-1024], true) with base 8 returns "2K", whereas it should return "1.0K" to align with other formats. The conversion is also broken for all other units(e.g. 1023M is also broken and returns "2G") commit 975525d38f60b2a3f3257dcf170226fc06b32f62 Author: aristocratos Date: Sun Aug 27 12:34:46 2023 +0200 Fix: Cpu gpu stats always shown when show_gpu_info is On and sizing issues commit 08abf0b9301e9d48f363ec8ffee161b351c4716d Author: aristocratos Date: Sun Aug 27 01:28:36 2023 +0200 Quickfixes for MacOS and FreeBSD compilation. commit 7290109f80175c8f140fa6abd67ad0183e1f21ec Author: aristocratos Date: Sun Aug 27 00:58:30 2023 +0200 Merge fix commit 283d4632427b0a5e3c8f8518e3db3fdd8b257f9c Merge: efddad4 c296ac1 Author: aristocratos Date: Sun Aug 27 00:56:22 2023 +0200 Merge branch 'main' into pr/romner-set/529 commit efddad42dce74ff4553307e30e4cfa58210dc377 Author: aristocratos Date: Sun Aug 27 00:39:57 2023 +0200 Changed: cpu_graph_lower Auto defaults to cpu_graph_upper when show_gpu_info is Off commit a9bc0874d48dfef157a95d075e5e81ad248faa87 Author: aristocratos Date: Sun Aug 27 00:31:07 2023 +0200 Added show_gpu_info setting and Auto options for cpu graphs commit b3970ee19cb6d240fcf40cd0077e61ae346da3af Author: aristocratos Date: Sat Aug 26 20:52:59 2023 +0200 Fixed: Key 5-0 gpu box toggle commit bd5d697830f65adb0ce6c4bd7e292e34c10079db Author: aristocratos Date: Sat Aug 26 20:29:43 2023 +0200 Squashed commit of the following: commit c296ac13cd4c16a11e137c309b7452bab096312e Merge: 9a1e760 091c30a Author: Jakob P. Liljenberg Date: Sat Aug 26 19:29:57 2023 +0200 Merge pull request #590 from nobounce/dangling-reference-config Convert parameters and config keys to std::string_view commit 9a1e760a661c9a160dd83e6d3ab710bf36b19b04 Merge: 9c8af4d 22e64ca Author: Jakob P. Liljenberg Date: Sat Aug 26 19:20:18 2023 +0200 Merge pull request #602 from jfouquart/main Fix getting zfs pool name with '.' char in freebsd commit 9c8af4df436c2847eefa66d2e0eb7ebfd75d70cf Merge: 8a49d8c 2217cbe Author: Jakob P. Liljenberg Date: Sat Aug 26 19:18:55 2023 +0200 Merge pull request #601 from joske/cleanup [macos] don't check /sys on macos commit 8a49d8cf456d0a15db65e7dc704d627b75a0fe43 Merge: 1556388 008fcd8 Author: Jakob P. Liljenberg Date: Sat Aug 26 19:18:07 2023 +0200 Merge pull request #600 from joske/makefile [macos/freebsd] support gcc13 commit 1556388c83644d122fab9241aa876232d94d1928 Merge: 1b126f5 d17e1a2 Author: Jakob P. Liljenberg Date: Sat Aug 26 19:14:00 2023 +0200 Merge pull request #599 from joske/main [macos] fix temp sensor on system with many cores commit d17e1a2dac79458940319d7117a21bdcd73ed53c Author: Jos Dehaes Date: Fri Aug 25 16:18:39 2023 +0200 fix some warnings commit 4d8aa6b11896dac99f81019e6dea11cc8d8856f1 Author: Jos Dehaes Date: Fri Aug 25 15:52:58 2023 +0200 fix core check commit 22e64caaff3d5877b7a494980a8ee3f17ea8f824 Author: Jonathan Fouquart Date: Fri Aug 25 09:37:49 2023 +0200 Fix getting zfs pool name with '.' char in freebsd commit 2217cbe143dd5aa45dbd50b4dc829577e2e1ccda Author: Jos Dehaes Date: Wed Aug 23 16:01:04 2023 +0200 [macos] don't check /sys on macos commit 008fcd889e862f1d378d331dab51b3d3ce9d9f3c Author: Jos Dehaes Date: Wed Aug 23 16:05:00 2023 +0200 also add g++13 commit 0fdca5eb0385253969e029fdfcf1fb9cff83ea33 Author: Jos Dehaes Date: Wed Aug 23 15:54:07 2023 +0200 support gcc13 commit dcbdb7360d44b4071ec0fe0757a0875a12147c8a Author: Jos Dehaes Date: Wed Aug 23 15:46:47 2023 +0200 [macos] fix temp sensor on system with many cores commit 1b126f55e38de76a2cca796593ef1554828d61e6 Author: aristocratos Date: Fri Aug 4 01:08:27 2023 +0200 Update Makefile for partial static compilation on freebsd commit c8ec6bbb000a865f14c50414e456955c473a2f3a Author: aristocratos Date: Thu Aug 3 23:08:33 2023 +0200 Fix freebsd nullptr changes and makefile for gcc12 and newer commit 8a33aab5885f828d7d0d2523aff31f9c33170332 Merge: 94e5c02 e4abcef Author: Jakob P. Liljenberg Date: Sun Jul 30 13:21:48 2023 +0200 Merge pull request #539 from nobounce/replace-NULL-nullptr Modernize using nullptr. commit 94e5c02d113f3fc8956d63ef4f0eecebfbf31b9d Author: aristocratos Date: Thu Jul 27 20:51:21 2023 +0200 Better text editing commit 091c30ab2be074836bb8d9a4f658cec9a5b36303 Author: nobounce Date: Thu Jul 27 14:17:54 2023 +0200 Convert parameters and config keys to std::string_view Using std::string_view instead of std::string& silences a new warning from GCC 13, -Wdangling-reference Also switch return type of `getI` from int& to int, trivial types are cheaper to copy by value commit e4abcefbf92e5d94ad169e1e47c0fbec7279fa6f Author: nobounce Date: Wed Jul 26 16:19:17 2023 +0200 Use nullptr instead of NULL. See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf TLDR: NULL is of type int and relies on proper implicit pointer conversion which may lead to issues when using overloaded functions It is also considered a 'best practise' for modern C++ and conveys the programmers intention more precisly. commit d53307f14cfa9cb416a3d1c8919d4f61cbb20bf7 Author: nobounce Date: Sun Jul 23 19:53:36 2023 +0200 Fix path to Linux CI file in itself The CI file has a list of dependent files including itself. The path was not updated when the CI was split into different files commit 594f42b9ebf886b70f6cdf0fb909d53eb6c5407f Merge: aca2e4b 53d6eba Author: Jakob P. Liljenberg Date: Wed Jul 26 15:38:01 2023 +0200 Merge pull request #584 from nobounce/nb/fix-ci-path Fix path to Linux CI file in itself commit aca2e4be7568186e122f1506fa25bdfb8c4f7f2a Author: aristocratos Date: Wed Jul 26 14:38:48 2023 +0200 Fix whitespace indent -> tab indent commit 33faa01910309895763011dce2a3194b889a0b6a Author: aristocratos Date: Wed Jul 26 14:34:15 2023 +0200 Revert fmt submodule to static fmt folder in include commit 53d6ebabc052a618001fa857eff25d5252a89210 Author: nobounce Date: Sun Jul 23 19:53:36 2023 +0200 Fix path to Linux CI file in itself The CI file has a list of dependent files including itself. The path was not updated when the CI was split into different files commit 346c9e479be1328c2fb133a457259b09884a1db3 Author: romner Date: Wed Jul 19 16:53:58 2023 +0200 Fix GPU text overflow in CPU panel, again commit 3a5e5fd5d37da33cbec48635f5c97502c824755b Author: romner Date: Wed Jul 19 16:22:45 2023 +0200 Improve 0-10 key input commit 972b2b6a011e9376090e1d14997550120eb37f36 Author: romner Date: Wed Jul 19 15:54:35 2023 +0200 Fix available boxes in menu & config description commit 1f73453aeca76cd04d7154c3bc82b6ebf64ba593 Author: romner Date: Wed Jul 19 15:34:23 2023 +0200 Fix crashes when trying to open nth GPU box with only n-1 GPUs in the system commit 46c6be0a296e922a265f1ad6ec0cfdcb487783d7 Author: romner Date: Sun Jul 16 17:19:09 2023 +0200 Fix GPU horizontal text overflow in CPU panel commit 85fb28cee6e6953ce5aed4725ce82bf65a7e5ade Author: romner Date: Fri Jul 14 02:39:44 2023 +0200 Fix RSMI_STATIC=true and add GPU section to README.md commit 3fad8a6fdec3b3efb5d4d407d969cec5e62ffa7a Author: romner-set Date: Mon Jun 26 13:10:31 2023 +0200 Add GPU options commit 746f716a027d0a76d0e48640052c73f9fc188450 Author: romner-set Date: Fri Jun 16 11:11:57 2023 +0200 Remove lib/rocm_smi_lib and add instructions for obtaining it to README commit d8ebbe1181346234edce4dfa030c3b63ab025942 Author: romner Date: Thu Jun 8 20:24:01 2023 +0200 Join NVML PCIe threads only if PCIe TX/RX is supported by GPU commit be1098915169cfb6a87c5fbad7b90cdb078b8257 Author: romner Date: Tue Jun 6 19:47:07 2023 +0200 Parallelize NVML PCIe TX/RX data collection commit 85892a9fe3968b9c72806eb0cb030cf77bd1e3e4 Author: aristocratos Date: Mon Jun 5 21:59:26 2023 +0200 Fix type: ulong -> size_t and compare std::cmp_less commit 85a10f0305cf0e8881c7ad02a8d3d483fe3186b0 Author: romner Date: Fri Jun 2 16:14:24 2023 +0200 Fix ROCm SMI makefile flags commit cd6979277dcc83f80e4e4f9671078592920f2177 Author: romner Date: Fri Jun 2 15:44:44 2023 +0200 Fix error when ROCm SMI static compilation fails commit daaa45324fb3f73a8f755277a91c2007c18b9bbc Author: romner Date: Fri Jun 2 15:34:12 2023 +0200 Load ROCm SMI dynamically by default, optionally statically compile and link commit 093edfe948c0ec4e55bb3f4e3888f13779db2886 Author: aristocratos Date: Thu Jun 1 19:49:00 2023 +0200 Minor changes in wording... commit b9a4d31fa48b33d6b892ebb9c828dd1ba3e7fde0 Author: aristocratos Date: Thu Jun 1 19:37:53 2023 +0200 Fix Makefile dependency order and layout commit a0163ce22007cc5016add0aeba567970d5f1d25e Author: romner Date: Thu Jun 1 16:42:02 2023 +0200 Statically link ROCm SMI commit b2df0696fdaacd7a4553180d065d160cb7405fe2 Author: romner-set Date: Thu Jun 1 03:41:56 2023 +0200 Dynamically load NVML commit 547f17dda33365f15610829448d0afa7472fc013 Author: romner-set Date: Tue May 30 18:24:50 2023 +0200 Add more GPU graph types to the CPU panel commit 842c761a731d64d477dce1181feadfe03de508cf Author: romner-set Date: Mon May 22 09:46:20 2023 +0200 Fix crash when all GPU panels are open but the CPU panel is closed commit 8c96bd51e96dfd602f3ba41da2e162f729a398dc Author: romner Date: Sun May 21 20:34:47 2023 +0200 Handle GPUs which cannot report certain stats in GPU panel commit 414d7eb94c6073823e7874c3947686c23929eb19 Author: romner Date: Sun May 21 18:02:50 2023 +0200 Handle GPUs which cannot report certain stats in btop_collect.cpp and CPU panel commit 005de97e6d1c162154bfa0255be83ba462f8f660 Author: aristocratos Date: Sun May 21 13:58:11 2023 +0200 Add missing fmt prefixes commit 1fee2bc08b9215ce728f16be2ee8a3068ae45618 Author: aristocratos Date: Sun May 21 13:52:19 2023 +0200 Add DebugTimer class and change some Logger::error calls to Logger::debug commit 2e68c0b916a4fb72c70b2fac2f7c25c8fb45b75b Author: aristocratos Date: Sat May 20 17:27:20 2023 +0200 Fixed key > gpu_names check commit 04ed16a9f6846a3231eee0016268e177b21a10db Author: aristocratos Date: Sat May 20 17:15:45 2023 +0200 Merged changes from main commit 8c710a2b687954bd66f07b02265da420bf645379 Author: aristocratos Date: Sat May 20 01:41:04 2023 +0200 Makefile auto detection and initial logic for excluding gpu code when libs are missing commit 8bae1ec092814368b04fb49827c7b89c327c62d6 Author: aristocratos Date: Sat May 20 00:13:00 2023 +0200 Fixed debug timer for gpu commit 01acfd603eda8b40829a6453d1fdb22f9e472238 Author: romner-set Date: Fri May 19 16:42:32 2023 +0200 Bind GPU panel to 5,6,7,8,9,0 and fully implement multi-GPU support commit 22a463976d107a1e5849621e2a11f3e1071ed884 Author: romner Date: Thu May 18 16:07:05 2023 +0200 Add GPU info to CPU panel commit c352bf26137daa0d242fa52ef11ecd6550bf0823 Author: romner-set Date: Mon May 15 19:42:55 2023 +0200 Add ROCm SMI backend for AMD GPU support commit 917d568a77b9e693a6d6a2c7fab57f957eff988d Author: romner-set Date: Mon May 15 13:58:54 2023 +0200 Add multi-GPU support for NVML data collection commit 2d27f2ff610d4894997ffbcede70ed5f716a4f94 Author: romner Date: Sun May 14 17:40:50 2023 +0200 Fix crash when no nvidia GPU is detected commit 0e0025a2c3dd521f9bcdb1bb9b0d76508a4663af Author: romner Date: Sun May 14 17:31:39 2023 +0200 Update makefile text, fix typo and adhere to contibuting guidelines commit bcffcdf19f4d2cb774b916f43493ec7c051ac92a Author: romner Date: Sun May 14 16:53:06 2023 +0200 Make GPU window's size dynamic and integrate it with the rest of btop commit 95b32283083ca2b74824a94ce97561b323ea1d17 Author: romner Date: Sat May 13 19:41:51 2023 +0200 Improve GPU side panel commit adcdc583b098156abc639a068238ef277f166153 Author: romner Date: Sat May 13 00:27:23 2023 +0200 Add GPU side panel commit d522a91ef4a95ccd2ff0df45271ff86fc119ab67 Author: romner Date: Fri May 12 19:34:47 2023 +0200 Add rudimentary, fullscreen single-GPU NVML utilization graph --- .editorconfig | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 5 +- .github/workflows/cmake-freebsd.yml | 40 + .github/workflows/cmake-linux.yml | 40 + .github/workflows/cmake-macos.yml | 47 + .../workflows/continuous-build-freebsd.yml | 24 +- .github/workflows/test-snap-can-build.yml | 42 + .gitignore | 22 +- CHANGELOG.md | 46 + CMakeLists.txt | 194 ++ Makefile | 101 +- README.md | 459 ++- cmake/Modules/Finddevstat.cmake | 23 + cmake/Modules/Findelf.cmake | 23 + cmake/Modules/Findkvm.cmake | 23 + include/robin_hood.h | 2544 ----------------- snap/snapcraft.yaml | 2 +- src/btop.cpp | 109 +- src/btop_config.cpp | 74 +- src/btop_config.hpp | 26 +- src/btop_draw.cpp | 679 ++++- src/btop_draw.hpp | 9 +- src/btop_input.cpp | 36 +- src/btop_input.hpp | 5 +- src/btop_menu.cpp | 202 +- src/btop_menu.hpp | 2 +- src/btop_shared.cpp | 19 + src/btop_shared.hpp | 114 +- src/btop_theme.cpp | 14 +- src/btop_theme.hpp | 9 +- src/btop_tools.cpp | 97 +- src/btop_tools.hpp | 132 +- src/freebsd/btop_collect.cpp | 35 +- src/linux/btop_collect.cpp | 835 +++++- src/osx/btop_collect.cpp | 38 +- src/osx/sensors.cpp | 3 + src/osx/sensors.hpp | 3 + themes/elementarish.theme | 50 +- themes/horizon.theme | 86 + 39 files changed, 3217 insertions(+), 2997 deletions(-) create mode 100644 .github/workflows/cmake-freebsd.yml create mode 100644 .github/workflows/cmake-linux.yml create mode 100644 .github/workflows/cmake-macos.yml create mode 100644 .github/workflows/test-snap-can-build.yml create mode 100644 CMakeLists.txt create mode 100644 cmake/Modules/Finddevstat.cmake create mode 100644 cmake/Modules/Findelf.cmake create mode 100644 cmake/Modules/Findkvm.cmake delete mode 100644 include/robin_hood.h create mode 100644 themes/horizon.theme diff --git a/.editorconfig b/.editorconfig index 63202f0..b27d25a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -[*.{cpp,h,sh,md,cfg,sample}] +[*.{cpp,h,hpp,sh,md,cfg,sample}] indent_style = tab indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3c937d1..7ed6a21 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -29,6 +29,7 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co **Info (please complete the following information):** - btop++ version: `btop -v` + - If using snap: `snap info btop` - Binary: [self compiled or static binary from release] - (If compiled) Compiler and version: - Architecture: [x86_64, aarch64, etc.] `uname -m` @@ -40,7 +41,9 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co **Additional context** -contents of `~/.config/btop/btop.log` +Contents of `~/.config/btop/btop.log` + +Note: The snap uses: `~/snap/btop/current/.config/btop` (try running btop with `--debug` flag if btop.log is empty) diff --git a/.github/workflows/cmake-freebsd.yml b/.github/workflows/cmake-freebsd.yml new file mode 100644 index 0000000..6e687f1 --- /dev/null +++ b/.github/workflows/cmake-freebsd.yml @@ -0,0 +1,40 @@ +name: FreeBSD CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-freebsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/freebsd/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-freebsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/freebsd/*pp' + +jobs: + cmake_build_on_freebsd: + runs-on: ubuntu-22.04 + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Compile + uses: vmactions/freebsd-vm@v1 + with: + release: '14.0' + usesh: true + prepare: pkg install -y cmake ninja + run: | + CXX=clang++ cmake -B build -G Ninja -DBTOP_STATIC=ON + cmake --build build --verbose + diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml new file mode 100644 index 0000000..49754d9 --- /dev/null +++ b/.github/workflows/cmake-linux.yml @@ -0,0 +1,40 @@ +name: Linux CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-linux.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/linux/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-linux.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/linux/*pp' + +jobs: + cmake_build_on_linux: + runs-on: ubuntu-latest + container: alpine:edge + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: apk add --no-cache --update clang cmake lld ninja + + - name: Configure + run: CXX=clang++ LDFLAGS=-fuse-ld=lld cmake -B build -G Ninja -DBTOP_STATIC=ON + + - name: Compile + run: cmake --build build --verbose + diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml new file mode 100644 index 0000000..32d6f7f --- /dev/null +++ b/.github/workflows/cmake-macos.yml @@ -0,0 +1,47 @@ +name: macOS CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-macos.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/osx/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-macos.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/osx/*pp' + +jobs: + cmake_build_on_macos: + runs-on: macos-latest + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: | + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + brew update --quiet + brew install --force --overwrite cmake llvm@17 ninja + + - name: Configure + run: | + export LLVM_PREFIX="$(brew --prefix llvm)" + export CXX="$LLVM_PREFIX/bin/clang++" + export CPPFLAGS="-I$LLVM_PREFIX/include" + export LDFLAGS="-L$LLVM_PREFIX/lib -L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++ -fuse-ld=$LLVM_PREFIX/bin/ld64.lld" + cmake -B build -G Ninja + + - name: Compile + run: cmake --build build --verbose + diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 5caa6d1..041133f 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -27,28 +27,34 @@ on: jobs: build-freebsd: - runs-on: macos-12 + runs-on: ubuntu-22.04 + timeout-minutes: 20 + strategy: + matrix: + compiler: ["clang++", "g++"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Compile - uses: vmactions/freebsd-vm@v0 + uses: vmactions/freebsd-vm@v1 with: - release: 13.2 + release: '14.0' usesh: true prepare: | - pkg install -y gmake gcc11 coreutils git - git config --global --add safe.directory /Users/runner/work/btop/btop + pkg install -y gmake gcc coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop run: | - gmake STATIC=true STRIP=true + CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") - mv bin/btop bin/btop-$GIT_HASH + COMPILER=$(echo ${{ matrix.compiler }} | sed 's/clang++/llvm/' | sed 's/g++/gcc/') + mv bin/btop bin/btop-"$COMPILER"-"$GIT_HASH" ls -alh bin - uses: actions/upload-artifact@v3 with: - name: btop-x86_64-FreeBSD-13.2 + name: btop-x86_64-freebsd-14 path: 'bin/*' if-no-files-found: error + diff --git a/.github/workflows/test-snap-can-build.yml b/.github/workflows/test-snap-can-build.yml new file mode 100644 index 0000000..c66df73 --- /dev/null +++ b/.github/workflows/test-snap-can-build.yml @@ -0,0 +1,42 @@ +name: 🧪 Test snap can be built on x86_64 + +on: + workflow_dispatch: + push: + branches: [ main ] + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/test-snap-can-build.yml' + pull_request: + branches: [ main ] + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/test-snap-can-build.yml' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v2 + + - uses: snapcore/action-build@v1 + id: build + + - uses: diddlesnaps/snapcraft-review-action@v1 + with: + snap: ${{ steps.build.outputs.snap }} + isClassic: 'false' diff --git a/.gitignore b/.gitignore index 25ee477..7e2ed8f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,9 +51,11 @@ bin btop .*/ +# Optional libraries +lib/rocm_smi_lib -#do not ignore .github directory -!.github +# Don't ignore .github directory +!.github/ # Ignore files created by Qt Creator *.config @@ -64,3 +66,19 @@ btop *.cxxflags *.files *.includes + +# CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# CLion +cmake-build-* diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4eceb..bbf273b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## v1.3.0 + +* Added Gpu Support | @romner-set | PR #529 + +* Enable macos clang | @muneebmahmed | PR #666 + +* Fix Apple Silicon CPUs misprinted | @masiboss | PR #679 + +* Cmake support for MacOS | @imwints | PR #675 + +* Elementarish theme: color update according to Elementary palette | @stradicat | PR #660 + +* Add alternative key codes for Delete, Insert, Home, End | @ivanp7 | PR #659 + +* Fix scrollbar not clearing sometimes. | @DecklynKern | PR #643 + +* Add keybind for toggling memory display mode in PROC box | @rahulaggarwal965 | PR #623 + +* Minor string initialization improvement | @imwints | PR #636 + +* Made disks statvfs logic asynchronous. | @crestfallnatwork | PR #633 + +* Fix signal list on non-linux/weird linux platforms | @lvxnull | PR #630 + +* Add option to accumulate a child's resources in parent in tree-view | @imwints | PR #618 + +* Add CMake support for Linux | @imwints | PR #589 + +* Horizon theme | @SidVeld | PR #610 + +* Fix short conversion of 1000-1023 *iB | @scorpion-26 | #609 + +* Fix integer overflows in btop_collect.cpp | @dorrellmw | #546 + +* Support compiling with LLVM | @imwints | #510 + +* Fix getting zfs pool name with '.' char in freebsd | @jfouquart | #602 + +* [macos/freebsd] support gcc13 | @joske | #600 + +* FreeBSD swap info | @rrveex | #560 + +* Create adwaita.theme | @flipflop133 | #485 + ++ Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn + ## v1.2.13 * Makefile: VERBOSE=true flag for Makefile to display all compiler commands and fixed so already set CXXFLAGS and LDFLAGS are displayed. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f8c546 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# CMake configuration for btop +# + +cmake_minimum_required(VERSION 3.24) + +# Disable in-source builds since they would override the Makefile +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + message(FATAL_ERROR "In-source builds are not allowed") +endif() + +project("btop" + VERSION 1.2.13 + DESCRIPTION "A monitor of resources" + HOMEPAGE_URL "https://github.com/aristocratos/btop" + LANGUAGES CXX +) + +include(CheckCXXCompilerFlag) +include(CheckIncludeFileCXX) +include(CheckIPOSupported) +include(CMakeDependentOption) + +# Make our Find.cmake files available +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") + +# When the build type is not set we can't fortify +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_COLOR_DIAGNOSTICS ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(BTOP_STATIC "Link btop statically" OFF) +option(BTOP_LTO "Enable LTO" ON) +option(BTOP_USE_MOLD "Use mold to link btop" OFF) +option(BTOP_PEDANTIC "Enable a bunch of additional warnings" OFF) +option(BTOP_WERROR "Compile with warnings as errors" OFF) +option(BTOP_GPU "Enable GPU support" ON) +cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF) + +if(BTOP_STATIC AND NOT APPLE) + # Set this before calling find_package + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() + +add_executable(btop + src/btop.cpp + src/btop_config.cpp + src/btop_draw.cpp + src/btop_input.cpp + src/btop_menu.cpp + src/btop_shared.cpp + src/btop_theme.cpp + src/btop_tools.cpp +) + +if(APPLE) + target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + target_sources(btop PRIVATE src/freebsd/btop_collect.cpp) +elseif(LINUX) + target_sources(btop PRIVATE src/linux/btop_collect.cpp) +else() + message(FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not supported") +endif() + +check_include_file_cxx(ranges CXX_HAVE_RANGES) +if(NOT CXX_HAVE_RANGES) + message(FATAL_ERROR "The compiler doesn't support ") +endif() + +# Check for and enable LTO +check_ipo_supported(RESULT ipo_supported) +if(ipo_supported AND BTOP_LTO) + set_target_properties(btop PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON) +endif() + +target_compile_options(btop PRIVATE -Wall -Wextra -Wpedantic -ftree-vectorize) + +if(BTOP_PEDANTIC) + target_compile_options(btop PRIVATE + -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual + -Wconversion -Wsign-conversion -Wdouble-promotion -Wformat=2 -Wimplicit-fallthrough -Weffc++ + $<$:-Wheader-hygiene -Wgnu -Wthread-safety> + $<$:-Wduplicated-cond -Wduplicated-branches -Wlogical-op> + $<$:-Wnull-dereference -Wuseless-cast> + ) +endif() +if(BTOP_WERROR) + target_compile_options(btop PRIVATE -Werror) +endif() + +if(NOT APPLE) + target_compile_options(btop PRIVATE -fstack-clash-protection) +endif() +check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR) +if(HAS_FSTACK_PROTECTOR) + target_compile_options(btop PRIVATE -fstack-protector) +endif() +check_cxx_compiler_flag(-fcf-protection HAS_FCF_PROTECTION) +if(HAS_FCF_PROTECTION) + target_compile_options(btop PRIVATE -fcf-protection) +endif() + +target_compile_definitions(btop PRIVATE + _FILE_OFFSET_BITS=64 + $<$:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> + # Only has an effect with optimizations enabled + $<$>:_FORTIFY_SOURCE=2> +) + +target_include_directories(btop SYSTEM PRIVATE include) + +# Enable pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(btop Threads::Threads) + +# Enable GPU support +if(LINUX AND BTOP_GPU) + target_compile_definitions(btop PRIVATE GPU_SUPPORT) + + if(BTOP_RSMI_STATIC) + # ROCm doesn't properly add it's folders to the module path if `CMAKE_MODULE_PATH` is already + # set + # We could also manully append ROCm's path here + set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH) + unset(CMAKE_MODULE_PATH) + + # NOTE: This might be problematic in the future if other sub projects depend on this or if + # btop starts producing libraries + # Build a static ROCm library + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + + add_subdirectory(lib/rocm_smi_lib EXCLUDE_FROM_ALL) + + add_library(ROCm INTERFACE) + # Export ROCm's properties to a target + target_compile_definitions(ROCm INTERFACE RSMI_STATIC) + target_include_directories(ROCm INTERFACE lib/rocm_smi_lib/include) + target_link_libraries(ROCm INTERFACE rocm_smi64) + + set(CMAKE_MODULE_PATH _CMAKE_MODULE_PATH) + + target_link_libraries(btop ROCm) + endif() +endif() + +if(BTOP_USE_MOLD) + target_link_options(btop PRIVATE -fuse-ld=mold) +endif() + +if(BTOP_STATIC) + target_compile_definitions(btop PRIVATE STATIC_BUILD) + target_link_options(btop PRIVATE -static LINKER:--fatal-warnings) +endif() + +# Other platform depdendent flags +if(APPLE) + target_link_libraries(btop + $ $ + ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + # Avoid version mismatch for libstdc++ when a specific version of GCC is installed and not the + # default one since all use the default ones RPATH + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + string(REGEX MATCH "^[0-9]+" GCC_VERSION_MAJOR "${CMAKE_CXX_COMPILER_VERSION}") + set_target_properties(btop PROPERTIES + INSTALL_RPATH "/usr/local/lib/gcc${GCC_VERSION_MAJOR}" + BUILD_WITH_INSTALL_RPATH TRUE + ) + endif() + + find_package(devstat REQUIRED) + target_link_libraries(btop devstat::devstat) + if(BTOP_STATIC) + find_package(elf REQUIRED) + find_package(kvm REQUIRED) + target_link_libraries(btop elf::elf kvm::kvm) + endif() +endif() + +install(TARGETS btop RUNTIME) +install(FILES "btop.desktop" DESTINATION "share/applications") +install(FILES "Img/icon.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "btop.png") +install(FILES "Img/icon.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg") +install(DIRECTORY "themes" DESTINATION "share/btop") + diff --git a/Makefile b/Makefile index 1ff479e..9f4db2e 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,7 @@ else endif ifneq ($(QUIET),true) - override PRE := info info-quiet override QUIET := false -else - override PRE := info-quiet endif OLDCXX := $(CXXFLAGS) @@ -39,6 +36,20 @@ endif override PLATFORM_LC := $(shell echo $(PLATFORM) | tr '[:upper:]' '[:lower:]') +#? GPU Support +ifeq ($(PLATFORM_LC)$(ARCH),linuxx86_64) + ifneq ($(STATIC),true) + GPU_SUPPORT := true + endif +endif +ifneq ($(GPU_SUPPORT),true) + GPU_SUPPORT := false +endif + +ifeq ($(GPU_SUPPORT),true) + override ADDFLAGS += -DGPU_SUPPORT +endif + #? Compiler and Linker ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0) override CXX_IS_CLANG := true @@ -48,10 +59,18 @@ override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1) CLANG_WORKS = false GCC_WORKS = false +MIN_CLANG_VERSION = 16 + +ifeq ($(DEBUG),true) + override ADDFLAGS += -DBTOP_DEBUG +endif #? Supported is Clang 16.0.0 and later ifeq ($(CXX_IS_CLANG),true) - ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 16; echo $$?),0) + ifeq ($(shell $(CXX) --version | grep Apple >/dev/null 2>&1; echo $$?),0) + MIN_CLANG_VERSION := 15 + endif + ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0) CLANG_WORKS := true endif endif @@ -214,24 +233,38 @@ endif P := %% -#? Default Make -all: $(PRE) directories btop +ifeq ($(VERBOSE),true) + # Doesn't work with `&>` + override SUPPRESS := > /dev/null 2> /dev/null +else + override SUPPRESS := +endif +#? Default Make +.ONESHELL: +all: | info rocm_smi info-quiet directories btop + +ifneq ($(QUIET),true) info: @printf " $(BANNER)\n" - @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" - @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" - @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" - @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" - @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" - @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" - @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" - @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" - @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" - @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" + @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" + @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" + @printf "\033[1;95mGPU_SUPPORT \033[1;94m:| \033[0m$(GPU_SUPPORT)\n" + @printf "\033[1;93mCXX \033[1;93m?| \033[0m$(CXX) \033[1;93m(\033[97m$(CXX_VERSION)\033[93m)\n" + @printf "\033[1;94mTHREADS \033[1;94m:| \033[0m$(THREADS)\n" + @printf "\033[1;92mREQFLAGS \033[1;91m!| \033[0m$(REQFLAGS)\n" + @printf "\033[1;91mWARNFLAGS \033[1;94m:| \033[0m$(WARNFLAGS)\n" + @printf "\033[1;94mOPTFLAGS \033[1;94m:| \033[0m$(OPTFLAGS)\n" + @printf "\033[1;93mLDCXXFLAGS \033[1;94m:| \033[0m$(LDCXXFLAGS)\n" + @printf "\033[1;95mCXXFLAGS \033[1;92m+| \033[0;37m\$$(\033[92mREQFLAGS\033[37m) \$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDCXX)\n" + @printf "\033[1;95mLDFLAGS \033[1;92m+| \033[0;37m\$$(\033[93mLDCXXFLAGS\033[37m) \$$(\033[94mOPTFLAGS\033[37m) \$$(\033[91mWARNFLAGS\033[37m) $(OLDLD)\n" +else +info: + @true +endif -info-quiet: - @sleep 0.1 2>/dev/null || true + +info-quiet: | info rocm_smi @printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n" help: @@ -258,11 +291,13 @@ directories: clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @rm -rf $(BUILDDIR) + @test -e lib/rocm_smi_lib/build && cmake --build lib/rocm_smi_lib/build --target clean &> /dev/null || true #? Clean Objects and Binaries distclean: clean @printf "\033[1;91mRemoving: \033[1;97mbuilt binaries...\033[0m\n" @rm -rf $(TARGETDIR) + @test -e lib/rocm_smi_lib/build && rm -rf lib/rocm_smi_lib/build || true install: @printf "\033[1;92mInstalling binary to: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\n" @@ -308,9 +343,35 @@ uninstall: #? Pull in dependency info for *existing* .o files -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) +#? Compile rocm_smi +ifeq ($(GPU_SUPPORT)$(RSMI_STATIC),truetrue) + ROCM_DIR ?= lib/rocm_smi_lib + ROCM_BUILD_DIR := $(ROCM_DIR)/build + ifeq ($(DEBUG),true) + BUILD_TYPE := Debug + else + BUILD_TYPE := Release + endif +.ONESHELL: +rocm_smi: + @printf "\n\033[1;92mBuilding ROCm SMI static library\033[37m...\033[0m\n" + @TSTAMP=$$(date +%s 2>/dev/null || echo "0") + @$(QUIET) || printf "\033[1;97mRunning CMake...\033[0m\n" + CXX=$(CXX) cmake -S $(ROCM_DIR) -B $(ROCM_BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DBUILD_SHARED_LIBS=OFF $(SUPPRESS) || { printf "\033[1;91mCMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; } + @$(QUIET) || printf "\n\033[1;97mBuilding and linking...\033[0m\n" + @cmake --build $(ROCM_BUILD_DIR) -j -t rocm_smi64 $(SUPPRESS) || { printf "\033[1;91mMake failed, continuing build without statically linking ROCm SMI\033[37m...\033[0m\n"; exit 0; } + @printf "\033[1;92m100$(P)\033[10D\033[5C-> \033[1;37m$(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a \033[1;93m(\033[1;97m$$(du -ah $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a | cut -f1)iB\033[1;93m)\033[0m\n" + @printf "\033[1;92mROCm SMI build complete in \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n" + @$(eval override LDFLAGS += $(ROCM_BUILD_DIR)/rocm_smi/librocm_smi64.a -DRSMI_STATIC) # TODO: this seems to execute every time, no matter if the compilation failed or succeeded + @$(eval override CXXFLAGS += -DRSMI_STATIC) +else +rocm_smi: + @true +endif + #? Link .ONESHELL: -btop: $(OBJECTS) | directories +btop: $(OBJECTS) | rocm_smi directories @sleep 0.2 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(QUIET) || printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n" @@ -321,7 +382,7 @@ btop: $(OBJECTS) | directories #? Compile .ONESHELL: -$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | directories +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories @sleep 0.3 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" diff --git a/README.md b/README.md index 2eecb90..f220e86 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,33 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) * [Configurability](#configurability) * [License](#license) ## News +##### 25 November 2023 + +GPU monitoring added for Linux! + +Compile from git main to try it out. + +Use keys `5`, `6`, `7` and `0` to show/hide the gpu monitoring boxes. `5` = Gpu 1, `6` = Gpu 2, etc. + +Gpu stats/graphs can also be displayed in the "Cpu box" (not as verbose), see the cpu options menu for info and configuration. + +Note that the binaries provided on the release page (when released) and the continuous builds will not have gpu support enabled. + +Because the GPU support relies on loading of dynamic gpu libraries, gpu support will not work when also static linking. + +See [Compilation Linux](#compilation-linux) for more info on how to compile with gpu monitoring support. + +Many thanks to [@romner-set](https://github.com/romner-set) who wrote the vast majority of the implementation for GPU support. + +Big update with version bump to 1.3 coming soon. + ##### 28 August 2022 [![btop4win](https://github.com/aristocratos/btop4win/raw/master/Img/logo.png)](https://github.com/aristocratos/btop4win) @@ -305,16 +326,46 @@ Also needs a UTF8 locale and a font that covers: ## Compilation Linux - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. The makefile also needs GNU coreutils and `sed` (should already be installed on any modern distribution). - For a `cmake` based build alternative see the [fork](https://github.com/jan-guenter/btop/tree/main) by @jan-guenter + ### GPU compatibility + + Btop++ supports NVIDIA and AMD GPUs out of the box on Linux x86_64, provided you have the correct drivers and libraries. + + Compatibility with Intel GPUs using generic DRM calls is planned, as is compatibility for FreeBSD and macOS. + + Gpu support will not work when static linking glibc (or musl, etc.)! + + For x86_64 Linux the flag `GPU_SUPPORT` is automatically set to `true`, to manually disable gpu support set the flag to false, like: + + `make GPU_SUPPORT=false` (or `cmake -DBTOP_GPU=false` with CMake) + + * **NVIDIA** + + You must use an official NVIDIA driver, both the closed-source and [open-source](https://github.com/NVIDIA/open-gpu-kernel-modules) ones have been verified to work. + + In addition to that you must also have the `nvidia-ml` dynamic library installed, which should be included with the driver package of your distribution. + + * **AMD** + + AMDGPU data is queried using the [ROCm SMI](https://github.com/RadeonOpenCompute/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag. + + This flag expects the ROCm SMI source code in `lib/rocm_smi_lib`, and compilation will fail if it's not there. The latest tested version is 5.6.x, which can be obtained with the following command: + + ```bash + git clone https://github.com/RadeonOpenCompute/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib + ``` + +
+ + +### With Make + 1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** - Use gcc-10 g++-10 if gcc-11 isn't available - ```bash sudo apt install coreutils sed git build-essential gcc-11 g++-11 ``` @@ -328,51 +379,51 @@ Also needs a UTF8 locale and a font that covers: 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation. - - Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc. - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - - If `g++` is linked to an older version of gcc on your system specify the correct version by appending `CXX=g++-10` or `CXX=g++-11`. - ```bash make ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `GPU_SUPPORT=` | Enable/disable GPU support (Enabled by default on X86_64 Linux) | + | `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + + Notice! If using LDAP Authentication, usernames will show as UID number for LDAP users if compiling statically with glibc. + 4. **Install** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```bash sudo make install ``` + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + Notice! Only use "sudo" when installing to a NON user owned directory. + 5. **(Optional) Set suid bit to make btop always run as root (or other user)** + ```bash + sudo make setuid + ``` + No need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems. Run after make install and use same PREFIX if any was used at install. Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `root` - ```bash - sudo make setuid - ``` - * **Uninstall** ```bash @@ -397,16 +448,94 @@ Also needs a UTF8 locale and a font that covers: make help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang / GCC, CMake, Ninja and Git + + For example, with Debian Bookworm: + + ```bash + sudo apt install cmake git g++ ninja-build + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + `````` + +3. **Compile** + + ```bash + # Configure + cmake -B build -G Ninja + # Build + cmake --build build + ``` + + This will automatically build a release version of btop. + + Some useful options to pass to the configure step: + + | Configure flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `-DBTOP_STATIC=` | Enables static linking (OFF by default) | + | `-DBTOP_LTO=` | Enables link time optimization (ON by default) | + | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | + | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | + | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_GPU=` | Enable GPU support (ON by default) | + | `-DBTOP_RSMI_STATIC=` | Build and link the ROCm SMI library statically (OFF by default) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + To force any other compiler, run `CXX= cmake -B build -G Ninja` + +4. **Install** + + ```bash + cmake --install build + ``` + + May require root privileges + +5. **Uninstall** + + CMake doesn't generate an uninstall target by default. To remove installed files, run + ``` + cat build/install_manifest.txt | xargs rm -irv + ``` + +6. **Cleanup build directory** + + ```bash + cmake --build build -t clean + ``` + +
+ ## Compilation macOS OSX - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. - GCC 12 needed for macOS Ventura. If you get linker errors on Ventura you'll need to upgrade your command line tools (Version 14.0) is bugged. + With GCC, version 12 (or better) is needed for macOS Ventura. If you get linker errors on Ventura you'll need to upgrade your command line tools (Version 14.0) is bugged. The makefile also needs GNU coreutils and `sed`. Install and use Homebrew or MacPorts package managers for easy dependency installation +
+ + +### With Make + + 1. **Install dependencies (example for Homebrew)** ```bash @@ -419,50 +548,49 @@ Also needs a UTF8 locale and a font that covers: git clone https://github.com/aristocratos/btop.git cd btop ``` - 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation (only libgcc and libstdc++ will be static!). - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - ```bash gmake ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + 4. **Install** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```bash sudo gmake install ``` + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + Notice! Only use "sudo" when installing to a NON user owned directory. + 5. **(Recommended) Set suid bit to make btop always run as root (or other user)** + ```bash + sudo gmake setuid + ``` + No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. Run after make install and use same PREFIX if any was used at install. Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` - ```bash - sudo gmake setuid - ``` - * **Uninstall** ```bash @@ -487,12 +615,92 @@ Also needs a UTF8 locale and a font that covers: gmake help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang, CMake, Ninja and Git + + ```bash + brew update --quiet + brew install cmake git llvm ninja + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + ```bash + # Configure + export LLVM_PREFIX="$(brew --prefix llvm)" + export CXX="$LLVM_PREFIX/bin/clang++" + export CPPFLAGS="-I$LLVM_PREFIX/include" + export LDFLAGS="-L$LLVM_PREFIX/lib -L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++ -fuse-ld=$LLVM_PREFIX/bin/ld64.lld" + cmake -B build -G Ninja + # Build + cmake --build build + ``` + + _**Note:** btop uses lots of C++ 20 features, so it's necessary to be specific about the compiler and the standard library. If you get a compile with Apple-Clang or GCC, feel free to add the instructions here._ + + This will automatically build a release version of btop. + + Some useful options to pass to the configure step: + + | Configure flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `-DBTOP_LTO=` | Enables link time optimization (ON by default) | + | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | + | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | + | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + To force any specific compiler, run `CXX= cmake -B build -G Ninja` + +4. **Install** + + ```bash + cmake --install build + ``` + + May require root privileges + +5. **Uninstall** + + CMake doesn't generate an uninstall target by default. To remove installed files, run + ``` + cat build/install_manifest.txt | xargs rm -irv + ``` + +6. **Cleanup build directory** + + ```bash + cmake --build build -t clean + ``` + +
+ ## Compilation FreeBSD - Needs GCC 10 or higher, (GCC 11 or above strongly recommended for better CPU efficiency in the compiled binary). + Requires at least GCC 10 or Clang 16. Note that GNU make (`gmake`) is required to compile on FreeBSD. +
+ + +### With gmake + + 1. **Install dependencies** ```bash @@ -508,47 +716,47 @@ Also needs a UTF8 locale and a font that covers: 3. **Compile** - Append `VERBOSE=true` to display full compiler/linker commands. - - Append `STATIC=true` for static compilation (only libgcc and libstdc++ will be static!). - - Append `QUIET=true` for less verbose output. - - Append `STRIP=true` to force stripping of debug symbols (adds `-s` linker flag). - - Append `ARCH=` to manually set the target architecture. - If omitted the makefile uses the machine triple (output of `-dumpmachine` compiler parameter) to detect the target system. - - Use `ADDFLAGS` variable for appending flags to both compiler and linker. - - For example: `ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. - ```bash gmake ``` + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + 4. **Install** - Append `PREFIX=/target/dir` to set target, default: `/usr/local` - - Notice! Only use "sudo" when installing to a NON user owned directory. - ```bash sudo gmake install ``` + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + Notice! Only use "sudo" when installing to a NON user owned directory. + 5. **(Recommended) Set suid bit to make btop always run as root (or other user)** + ```bash + sudo gmake setuid + ``` + No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. Run after make install and use same PREFIX if any was used at install. Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` - ```bash - sudo gmake setuid - ``` - * **Uninstall** ```bash @@ -573,6 +781,93 @@ Also needs a UTF8 locale and a font that covers: gmake help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires Clang / GCC, CMake, Ninja and Git + + _**Note:** LLVM's libc++ shipped with FreeBSD 13 is too old and cannot compile btop._ + + FreeBSD 14 and later: + ```bash + pkg install cmake ninja + ``` + + FreeBSD 13: + ```bash + pkg install cmake gcc13 ninja + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + FreeBSD 14 and later: + ```bash + # Configure + cmake -B build -G Ninja + # Build + cmake --build build + ``` + + FreeBSD 13: + ```bash + # Configure + CXX=g++13 cmake -B build -G Ninja + # Build + cmake --build build + ``` + + This will automatically build a release version of btop. + + Some useful options to pass to the configure step: + + | Configure flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `-DBTOP_STATIC=` | Enables static linking (OFF by default) | + | `-DBTOP_LTO=` | Enables link time optimization (ON by default) | + | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | + | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | + | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + _**Note:** Static linking does not work with GCC._ + + To force any other compiler, run `CXX= cmake -B build -G Ninja` + +4. **Install** + + ```bash + cmake --install build + ``` + + May require root privileges + +5. **Uninstall** + + CMake doesn't generate an uninstall target by default. To remove installed files, run + ``` + cat build/install_manifest.txt | xargs rm -irv + ``` + +6. **Cleanup build directory** + + ```bash + cmake --build build -t clean + ``` + +
+ ## Installing the snap [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) @@ -657,7 +952,7 @@ graph_symbol_net = "default" # Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty". graph_symbol_proc = "default" -#* Manually set which boxes to show. Available values are "cpu mem net proc", separate values with whitespace. +#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace. shown_boxes = "proc cpu mem net" #* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs. diff --git a/cmake/Modules/Finddevstat.cmake b/cmake/Modules/Finddevstat.cmake new file mode 100644 index 0000000..694e613 --- /dev/null +++ b/cmake/Modules/Finddevstat.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find devstat, the Device Statistics Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(devstat_INCLUDE_DIR NAMES devstat.h) + find_library(devstat_LIBRARY NAMES devstat) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(devstat REQUIRED_VARS devstat_LIBRARY devstat_INCLUDE_DIR) + + if(devstat_FOUND AND NOT TARGET devstat::devstat) + add_library(devstat::devstat UNKNOWN IMPORTED) + set_target_properties(devstat::devstat PROPERTIES + IMPORTED_LOCATION "${devstat_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${devstat_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(devstat_INCLUDE_DIR devstat_LIBRARY) +endif() + diff --git a/cmake/Modules/Findelf.cmake b/cmake/Modules/Findelf.cmake new file mode 100644 index 0000000..91e0beb --- /dev/null +++ b/cmake/Modules/Findelf.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find libelf, the ELF Access Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(elf_INCLUDE_DIR NAMES libelf.h) + find_library(elf_LIBRARY NAMES elf) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(elf REQUIRED_VARS elf_LIBRARY elf_INCLUDE_DIR) + + if(elf_FOUND AND NOT TARGET elf::elf) + add_library(elf::elf UNKNOWN IMPORTED) + set_target_properties(elf::elf PROPERTIES + IMPORTED_LOCATION "${elf_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${elf_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(elf_INCLUDE_DIR elf_LIBRARY) +endif() + diff --git a/cmake/Modules/Findkvm.cmake b/cmake/Modules/Findkvm.cmake new file mode 100644 index 0000000..a0847de --- /dev/null +++ b/cmake/Modules/Findkvm.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find libkvm, the Kernel Data Access Library +# + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + find_path(kvm_INCLUDE_DIR NAMES kvm.h) + find_library(kvm_LIBRARY NAMES kvm) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(kvm REQUIRED_VARS kvm_LIBRARY kvm_INCLUDE_DIR) + + if(kvm_FOUND AND NOT TARGET kvm::kvm) + add_library(kvm::kvm UNKNOWN IMPORTED) + set_target_properties(kvm::kvm PROPERTIES + IMPORTED_LOCATION "${kvm_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${kvm_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(kvm_INCLUDE_DIR kvm_LIBRARY) +endif() + diff --git a/include/robin_hood.h b/include/robin_hood.h deleted file mode 100644 index 0af031f..0000000 --- a/include/robin_hood.h +++ /dev/null @@ -1,2544 +0,0 @@ -// ______ _____ ______ _________ -// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / -// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / -// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / -// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ -// _/_____/ -// -// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 -// https://github.com/martinus/robin-hood-hashing -// -// Licensed under the MIT License . -// SPDX-License-Identifier: MIT -// Copyright (c) 2018-2021 Martin Ankerl -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef ROBIN_HOOD_H_INCLUDED -#define ROBIN_HOOD_H_INCLUDED - -// see https://semver.org/ -#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes -#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes - -#include -#include -#include -#include -#include -#include // only to support hash of smart pointers -#include -#include -#include -#include -#if __cplusplus >= 201703L -# include -#endif - -// #define ROBIN_HOOD_LOG_ENABLED -#ifdef ROBIN_HOOD_LOG_ENABLED -# include -# define ROBIN_HOOD_LOG(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_LOG(x) -#endif - -// #define ROBIN_HOOD_TRACE_ENABLED -#ifdef ROBIN_HOOD_TRACE_ENABLED -# include -# define ROBIN_HOOD_TRACE(...) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; -#else -# define ROBIN_HOOD_TRACE(x) -#endif - -// #define ROBIN_HOOD_COUNT_ENABLED -#ifdef ROBIN_HOOD_COUNT_ENABLED -# include -# define ROBIN_HOOD_COUNT(x) ++counts().x; -namespace robin_hood { -struct Counts { - uint64_t shiftUp{}; - uint64_t shiftDown{}; -}; -inline std::ostream& operator<<(std::ostream& os, Counts const& c) { - return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; -} - -static Counts& counts() { - static Counts counts{}; - return counts; -} -} // namespace robin_hood -#else -# define ROBIN_HOOD_COUNT(x) -#endif - -// all non-argument macros should use this facility. See -// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() - -// mark unused members with this macro -#define ROBIN_HOOD_UNUSED(identifier) - -// bitness -#if SIZE_MAX == UINT32_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 -#elif SIZE_MAX == UINT64_MAX -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 -#else -# error Unsupported bitness -#endif - -// endianess -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#endif - -// inline -#ifdef _MSC_VER -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) -#endif - -// exceptions -#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 -#endif - -// count leading/trailing bits -#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) -# ifdef _MSC_VER -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 -# endif -# include -# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ - [](size_t mask) noexcept -> int { \ - unsigned long index; \ - return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ - : ROBIN_HOOD(BITNESS); \ - }(x) -# else -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll -# endif -# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) -# endif -#endif - -// fallthrough -#ifndef __has_cpp_attribute // For backwards compatibility -# define __has_cpp_attribute(x) 0 -#endif -#if __has_cpp_attribute(clang::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] -#elif __has_cpp_attribute(gnu::fallthrough) -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() -#endif - -// likely/unlikely -#ifdef _MSC_VER -# define ROBIN_HOOD_LIKELY(condition) condition -# define ROBIN_HOOD_UNLIKELY(condition) condition -#else -# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) -# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) -#endif - -// detect if native wchar_t type is availiable in MSVC -#ifdef _MSC_VER -# ifdef _NATIVE_WCHAR_T_DEFINED -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 -#endif - -// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr -#ifdef _MSC_VER -# if _MSC_VER <= 1900 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -# endif -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 -#endif - -// workaround missing "is_trivially_copyable" in g++ < 5.0 -// See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) -#else -# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value -#endif - -// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L -#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] -#else -# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() -#endif - -namespace robin_hood { - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) -# define ROBIN_HOOD_STD std -#else - -// c++11 compatibility layer -namespace ROBIN_HOOD_STD { -template -struct alignment_of - : std::integral_constant::type)> {}; - -template -class integer_sequence { -public: - using value_type = T; - static_assert(std::is_integral::value, "not integral type"); - static constexpr std::size_t size() noexcept { - return sizeof...(Ints); - } -}; -template -using index_sequence = integer_sequence; - -namespace detail_ { -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); - - template - struct IntSeqCombiner; - - template - struct IntSeqCombiner, integer_sequence> { - using TResult = integer_sequence; - }; - - using TResult = - typename IntSeqCombiner::TResult, - typename IntSeqImpl::TResult>::TResult; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; - -template -struct IntSeqImpl { - using TValue = T; - static_assert(std::is_integral::value, "not integral type"); - static_assert(Begin >= 0, "unexpected argument (Begin<0)"); - using TResult = integer_sequence; -}; -} // namespace detail_ - -template -using make_integer_sequence = typename detail_::IntSeqImpl::TResult; - -template -using make_index_sequence = make_integer_sequence; - -template -using index_sequence_for = make_index_sequence; - -} // namespace ROBIN_HOOD_STD - -#endif - -namespace detail { - -// make sure we static_cast to the correct type for hash_int -#if ROBIN_HOOD(BITNESS) == 64 -using SizeT = uint64_t; -#else -using SizeT = uint32_t; -#endif - -template -T rotr(T x, unsigned k) { - return (x >> k) | (x << (8U * sizeof(T) - k)); -} - -// This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to -// 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with -// care! -template -inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { - return reinterpret_cast(ptr); -} - -template -inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { - return reinterpret_cast(ptr); -} - -// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other -// inlinings more difficult. Throws are also generally the slow path. -template -[[noreturn]] ROBIN_HOOD(NOINLINE) -#if ROBIN_HOOD(HAS_EXCEPTIONS) - void doThrow(Args&&... args) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - throw E(std::forward(args)...); -} -#else - void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { - abort(); -} -#endif - -template -T* assertNotNull(T* t, Args&&... args) { - if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { - doThrow(std::forward(args)...); - } - return t; -} - -template -inline T unaligned_load(void const* ptr) noexcept { - // using memcpy so we don't get into unaligned load problems. - // compiler should optimize this very well anyways. - T t; - std::memcpy(&t, ptr, sizeof(T)); - return t; -} - -// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, -// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a -// pointer. -template -class BulkPoolAllocator { -public: - BulkPoolAllocator() noexcept = default; - - // does not copy anything, just creates a new allocator. - BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept - : mHead(nullptr) - , mListForFree(nullptr) {} - - BulkPoolAllocator(BulkPoolAllocator&& o) noexcept - : mHead(o.mHead) - , mListForFree(o.mListForFree) { - o.mListForFree = nullptr; - o.mHead = nullptr; - } - - BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { - reset(); - mHead = o.mHead; - mListForFree = o.mListForFree; - o.mListForFree = nullptr; - o.mHead = nullptr; - return *this; - } - - BulkPoolAllocator& - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { - // does not do anything - return *this; - } - - ~BulkPoolAllocator() noexcept { - reset(); - } - - // Deallocates all allocated memory. - void reset() noexcept { - while (mListForFree) { - T* tmp = *mListForFree; - ROBIN_HOOD_LOG("std::free") - std::free(mListForFree); - mListForFree = reinterpret_cast_no_cast_align_warning(tmp); - } - mHead = nullptr; - } - - // allocates, but does NOT initialize. Use in-place new constructor, e.g. - // T* obj = pool.allocate(); - // ::new (static_cast(obj)) T(); - T* allocate() { - T* tmp = mHead; - if (!tmp) { - tmp = performAllocation(); - } - - mHead = *reinterpret_cast_no_cast_align_warning(tmp); - return tmp; - } - - // does not actually deallocate but puts it in store. - // make sure you have already called the destructor! e.g. with - // obj->~T(); - // pool.deallocate(obj); - void deallocate(T* obj) noexcept { - *reinterpret_cast_no_cast_align_warning(obj) = mHead; - mHead = obj; - } - - // Adds an already allocated block of memory to the allocator. This allocator is from now on - // responsible for freeing the data (with free()). If the provided data is not large enough to - // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. - void addOrFree(void* ptr, const size_t numBytes) noexcept { - // calculate number of available elements in ptr - if (numBytes < ALIGNMENT + ALIGNED_SIZE) { - // not enough data for at least one element. Free and return. - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } else { - ROBIN_HOOD_LOG("add to buffer") - add(ptr, numBytes); - } - } - - void swap(BulkPoolAllocator& other) noexcept { - using std::swap; - swap(mHead, other.mHead); - swap(mListForFree, other.mListForFree); - } - -private: - // iterates the list of allocated memory to calculate how many to alloc next. - // Recalculating this each time saves us a size_t member. - // This ignores the fact that memory blocks might have been added manually with addOrFree. In - // practice, this should not matter much. - ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { - auto tmp = mListForFree; - size_t numAllocs = MinNumAllocs; - - while (numAllocs * 2 <= MaxNumAllocs && tmp) { - auto x = reinterpret_cast(tmp); - tmp = *x; - numAllocs *= 2; - } - - return numAllocs; - } - - // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). - void add(void* ptr, const size_t numBytes) noexcept { - const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; - - auto data = reinterpret_cast(ptr); - - // link free list - auto x = reinterpret_cast(data); - *x = mListForFree; - mListForFree = data; - - // create linked list for newly allocated data - auto* const headT = - reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); - - auto* const head = reinterpret_cast(headT); - - // Visual Studio compiler automatically unrolls this loop, which is pretty cool - for (size_t i = 0; i < numElements; ++i) { - *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = - head + (i + 1) * ALIGNED_SIZE; - } - - // last one points to 0 - *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = - mHead; - mHead = headT; - } - - // Called when no memory is available (mHead == 0). - // Don't inline this slow path. - ROBIN_HOOD(NOINLINE) T* performAllocation() { - size_t const numElementsToAlloc = calcNumElementsToAlloc(); - - // alloc new memory: [prev |T, T, ... T] - size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; - ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE - << " * " << numElementsToAlloc) - add(assertNotNull(std::malloc(bytes)), bytes); - return mHead; - } - - // enforce byte alignment of the T's -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) - static constexpr size_t ALIGNMENT = - (std::max)(std::alignment_of::value, std::alignment_of::value); -#else - static const size_t ALIGNMENT = - (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) - ? ROBIN_HOOD_STD::alignment_of::value - : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround -#endif - - static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; - - static_assert(MinNumAllocs >= 1, "MinNumAllocs"); - static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); - static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); - static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); - static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); - - T* mHead{nullptr}; - T** mListForFree{nullptr}; -}; - -template -struct NodeAllocator; - -// dummy allocator that does nothing -template -struct NodeAllocator { - - // we are not using the data, so just free it. - void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { - ROBIN_HOOD_LOG("std::free") - std::free(ptr); - } -}; - -template -struct NodeAllocator : public BulkPoolAllocator {}; - -// c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making -// my own here. -namespace swappable { -#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) -using std::swap; -template -struct nothrow { - static const bool value = noexcept(swap(std::declval(), std::declval())); -}; -#else -template -struct nothrow { - static const bool value = std::is_nothrow_swappable::value; -}; -#endif -} // namespace swappable - -} // namespace detail - -struct is_transparent_tag {}; - -// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, -// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is -// also tested. -template -struct pair { - using first_type = T1; - using second_type = T2; - - template ::value && - std::is_default_constructible::value>::type> - constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) - : first() - , second() {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair const& o) noexcept( - noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) - : first(o.first) - , second(o.second) {} - - // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair&& o) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(o.first)) - , second(std::move(o.second)) {} - - constexpr pair(T1&& a, T2&& b) noexcept(noexcept( - T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) - : first(std::move(a)) - , second(std::move(b)) {} - - template - constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( - std::declval()))) && noexcept(T2(std::forward(std::declval())))) - : first(std::forward(a)) - , second(std::forward(b)) {} - - template - // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" - // if this constructor is constexpr -#if !ROBIN_HOOD(BROKEN_CONSTEXPR) - constexpr -#endif - pair(std::piecewise_construct_t /*unused*/, std::tuple a, - std::tuple - b) noexcept(noexcept(pair(std::declval&>(), - std::declval&>(), - ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()))) - : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()) { - } - - // constructor called from the std::piecewise_construct_t ctor - template - pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( - noexcept(T1(std::forward(std::get( - std::declval&>()))...)) && noexcept(T2(std:: - forward(std::get( - std::declval&>()))...))) - : first(std::forward(std::get(a))...) - , second(std::forward(std::get(b))...) { - // make visual studio compiler happy about warning about unused a & b. - // Visual studio's pair implementation disables warning 4100. - (void)a; - (void)b; - } - - void swap(pair& o) noexcept((detail::swappable::nothrow::value) && - (detail::swappable::nothrow::value)) { - using std::swap; - swap(first, o.first); - swap(second, o.second); - } - - T1 first; // NOLINT(misc-non-private-member-variables-in-classes) - T2 second; // NOLINT(misc-non-private-member-variables-in-classes) -}; - -template -inline void swap(pair& a, pair& b) noexcept( - noexcept(std::declval&>().swap(std::declval&>()))) { - a.swap(b); -} - -template -inline constexpr bool operator==(pair const& x, pair const& y) { - return (x.first == y.first) && (x.second == y.second); -} -template -inline constexpr bool operator!=(pair const& x, pair const& y) { - return !(x == y); -} -template -inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( - std::declval
() < std::declval()) && noexcept(std::declval() < - std::declval())) { - return x.first < y.first || (!(y.first < x.first) && x.second < y.second); -} -template -inline constexpr bool operator>(pair const& x, pair const& y) { - return y < x; -} -template -inline constexpr bool operator<=(pair const& x, pair const& y) { - return !(x > y); -} -template -inline constexpr bool operator>=(pair const& x, pair const& y) { - return !(x < y); -} - -inline size_t hash_bytes(void const* ptr, size_t len) noexcept { - static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); - static constexpr uint64_t seed = UINT64_C(0xe17a1465); - static constexpr unsigned int r = 47; - - auto const* const data64 = static_cast(ptr); - uint64_t h = seed ^ (len * m); - - size_t const n_blocks = len / 8; - for (size_t i = 0; i < n_blocks; ++i) { - auto k = detail::unaligned_load(data64 + i); - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - auto const* const data8 = reinterpret_cast(data64 + n_blocks); - switch (len & 7U) { - case 7: - h ^= static_cast(data8[6]) << 48U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 6: - h ^= static_cast(data8[5]) << 40U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 5: - h ^= static_cast(data8[4]) << 32U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 4: - h ^= static_cast(data8[3]) << 24U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 3: - h ^= static_cast(data8[2]) << 16U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 2: - h ^= static_cast(data8[1]) << 8U; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - case 1: - h ^= static_cast(data8[0]); - h *= m; - ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH - default: - break; - } - - h ^= h >> r; - - // not doing the final step here, because this will be done by keyToIdx anyways - // h *= m; - // h ^= h >> r; - return static_cast(h); -} - -inline size_t hash_int(uint64_t x) noexcept { - // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, - // and doesn't need any special 128bit operations. - x ^= x >> 33U; - x *= UINT64_C(0xff51afd7ed558ccd); - x ^= x >> 33U; - - // not doing the final step here, because this will be done by keyToIdx anyways - // x *= UINT64_C(0xc4ceb9fe1a85ec53); - // x ^= x >> 33U; - return static_cast(x); -} - -// A thin wrapper around std::hash, performing an additional simple mixing step of the result. -template -struct hash : public std::hash { - size_t operator()(T const& obj) const - noexcept(noexcept(std::declval>().operator()(std::declval()))) { - // call base hash - auto result = std::hash::operator()(obj); - // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); - } -}; - -template -struct hash> { - size_t operator()(std::basic_string const& str) const noexcept { - return hash_bytes(str.data(), sizeof(CharT) * str.size()); - } -}; - -#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) -template -struct hash> { - size_t operator()(std::basic_string_view const& sv) const noexcept { - return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); - } -}; -#endif - -template -struct hash { - size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); - } -}; - -template -struct hash> { - size_t operator()(std::unique_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash> { - size_t operator()(std::shared_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); - } -}; - -template -struct hash::value>::type> { - size_t operator()(Enum e) const noexcept { - using Underlying = typename std::underlying_type::type; - return hash{}(static_cast(e)); - } -}; - -#define ROBIN_HOOD_HASH_INT(T) \ - template <> \ - struct hash { \ - size_t operator()(T const& obj) const noexcept { \ - return hash_int(static_cast(obj)); \ - } \ - } - -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif -// see https://en.cppreference.com/w/cpp/utility/hash -ROBIN_HOOD_HASH_INT(bool); -ROBIN_HOOD_HASH_INT(char); -ROBIN_HOOD_HASH_INT(signed char); -ROBIN_HOOD_HASH_INT(unsigned char); -ROBIN_HOOD_HASH_INT(char16_t); -ROBIN_HOOD_HASH_INT(char32_t); -#if ROBIN_HOOD(HAS_NATIVE_WCHART) -ROBIN_HOOD_HASH_INT(wchar_t); -#endif -ROBIN_HOOD_HASH_INT(short); -ROBIN_HOOD_HASH_INT(unsigned short); -ROBIN_HOOD_HASH_INT(int); -ROBIN_HOOD_HASH_INT(unsigned int); -ROBIN_HOOD_HASH_INT(long); -ROBIN_HOOD_HASH_INT(long long); -ROBIN_HOOD_HASH_INT(unsigned long); -ROBIN_HOOD_HASH_INT(unsigned long long); -#if defined(__GNUC__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif -namespace detail { - -template -struct void_type { - using type = void; -}; - -template -struct has_is_transparent : public std::false_type {}; - -template -struct has_is_transparent::type> - : public std::true_type {}; - -// using wrapper classes for hash and key_equal prevents the diamond problem when the same type -// is used. see https://stackoverflow.com/a/28771920/48181 -template -struct WrapHash : public T { - WrapHash() = default; - explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -template -struct WrapKeyEqual : public T { - WrapKeyEqual() = default; - explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) - : T(o) {} -}; - -// A highly optimized hashmap implementation, using the Robin Hood algorithm. -// -// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but -// be about 2x faster in most cases and require much less allocations. -// -// This implementation uses the following memory layout: -// -// [Node, Node, ... Node | info, info, ... infoSentinel ] -// -// * Node: either a DataNode that directly has the std::pair as member, -// or a DataNode with a pointer to std::pair. Which DataNode representation to use -// depends on how fast the swap() operation is. Heuristically, this is automatically choosen -// based on sizeof(). there are always 2^n Nodes. -// -// * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. -// Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the -// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it -// actually belongs to the previous position and was pushed out because that place is already -// taken. -// -// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the -// need for a idx variable. -// -// According to STL, order of templates has effect on throughput. That's why I've moved the -// boolean to the front. -// https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ -template -class Table - : public WrapHash, - public WrapKeyEqual, - detail::NodeAllocator< - typename std::conditional< - std::is_void::value, Key, - robin_hood::pair::type, T>>::type, - 4, 16384, IsFlat> { -public: - static constexpr bool is_flat = IsFlat; - static constexpr bool is_map = !std::is_void::value; - static constexpr bool is_set = !is_map; - static constexpr bool is_transparent = - has_is_transparent::value && has_is_transparent::value; - - using key_type = Key; - using mapped_type = T; - using value_type = typename std::conditional< - is_set, Key, - robin_hood::pair::type, T>>::type; - using size_type = size_t; - using hasher = Hash; - using key_equal = KeyEqual; - using Self = Table; - -private: - static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, - "MaxLoadFactor100 needs to be >10 && < 100"); - - using WHash = WrapHash; - using WKeyEqual = WrapKeyEqual; - - // configuration defaults - - // make sure we have 8 elements, needed to quickly rehash mInfo - static constexpr size_t InitialNumElements = sizeof(uint64_t); - static constexpr uint32_t InitialInfoNumBits = 5; - static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; - static constexpr size_t InfoMask = InitialInfoInc - 1U; - static constexpr uint8_t InitialInfoHashShift = 0; - using DataPool = detail::NodeAllocator; - - // type needs to be wider than uint8_t. - using InfoType = uint32_t; - - // DataNode //////////////////////////////////////////////////////// - - // Primary template for the data node. We have special implementations for small and big - // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these - // on the heap so swap merely swaps a pointer. - template - class DataNode {}; - - // Small: just allocate on the stack. - template - class DataNode final { - public: - template - explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( - noexcept(value_type(std::forward(args)...))) - : mData(std::forward(args)...) {} - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( - std::is_nothrow_move_constructible::value) - : mData(std::move(n.mData)) {} - - // doesn't do anything - void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} - void destroyDoNotDeallocate() noexcept {} - - value_type const* operator->() const noexcept { - return &mData; - } - value_type* operator->() noexcept { - return &mData; - } - - const value_type& operator*() const noexcept { - return mData; - } - - value_type& operator*() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData.first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData.second; - } - - void swap(DataNode& o) noexcept( - noexcept(std::declval().swap(std::declval()))) { - mData.swap(o.mData); - } - - private: - value_type mData; - }; - - // big object: allocate on heap. - template - class DataNode { - public: - template - explicit DataNode(M& map, Args&&... args) - : mData(map.allocate()) { - ::new (static_cast(mData)) value_type(std::forward(args)...); - } - - DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept - : mData(std::move(n.mData)) {} - - void destroy(M& map) noexcept { - // don't deallocate, just put it into list of datapool. - mData->~value_type(); - map.deallocate(mData); - } - - void destroyDoNotDeallocate() noexcept { - mData->~value_type(); - } - - value_type const* operator->() const noexcept { - return mData; - } - - value_type* operator->() noexcept { - return mData; - } - - const value_type& operator*() const { - return *mData; - } - - value_type& operator*() { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type - getFirst() const noexcept { - return mData->first; - } - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getFirst() const noexcept { - return *mData; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() noexcept { - return mData->second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::type getSecond() const noexcept { - return mData->second; - } - - void swap(DataNode& o) noexcept { - using std::swap; - swap(mData, o.mData); - } - - private: - value_type* mData; - }; - - using Node = DataNode; - - // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { - return n.getFirst(); - } - - // in case we have void mapped_type, we are not using a pair, thus we just route k through. - // No need to disable this because it's just not used if not applicable. - ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { - return k; - } - - // in case we have non-void mapped_type, we have a standard robin_hood::pair - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, key_type const&>::type - getFirstConst(value_type const& vt) const noexcept { - return vt.first; - } - - // Cloner ////////////////////////////////////////////////////////// - - template - struct Cloner; - - // fast path: Just copy data, without allocating anything. - template - struct Cloner { - void operator()(M const& source, M& target) const { - auto const* const src = reinterpret_cast(source.mKeyVals); - auto* tgt = reinterpret_cast(target.mKeyVals); - auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); - std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); - } - }; - - template - struct Cloner { - void operator()(M const& s, M& t) const { - auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); - std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); - - for (size_t i = 0; i < numElementsWithBuffer; ++i) { - if (t.mInfo[i]) { - ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); - } - } - } - }; - - // Destroyer /////////////////////////////////////////////////////// - - template - struct Destroyer {}; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - } - }; - - template - struct Destroyer { - void nodes(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroy(m); - n.~Node(); - } - } - } - - void nodesDoNotDeallocate(M& m) const noexcept { - m.mNumElements = 0; - // clear also resets mInfo to 0, that's sometimes not necessary. - auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); - for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { - if (0 != m.mInfo[idx]) { - Node& n = m.mKeyVals[idx]; - n.destroyDoNotDeallocate(); - n.~Node(); - } - } - } - }; - - // Iter //////////////////////////////////////////////////////////// - - struct fast_forward_tag {}; - - // generic iterator for both const_iterator and iterator. - template - // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) - class Iter { - private: - using NodePtr = typename std::conditional::type; - - public: - using difference_type = std::ptrdiff_t; - using value_type = typename Self::value_type; - using reference = typename std::conditional::type; - using pointer = typename std::conditional::type; - using iterator_category = std::forward_iterator_tag; - - // default constructed iterator can be compared to itself, but WON'T return true when - // compared to end(). - Iter() = default; - - // Rule of zero: nothing specified. The conversion constructor is only enabled for - // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. - - // Conversion constructor from iterator to const_iterator. - template ::type> - // NOLINTNEXTLINE(hicpp-explicit-conversions) - Iter(Iter const& other) noexcept - : mKeyVals(other.mKeyVals) - , mInfo(other.mInfo) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) {} - - Iter(NodePtr valPtr, uint8_t const* infoPtr, - fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept - : mKeyVals(valPtr) - , mInfo(infoPtr) { - fastForward(); - } - - template ::type> - Iter& operator=(Iter const& other) noexcept { - mKeyVals = other.mKeyVals; - mInfo = other.mInfo; - return *this; - } - - // prefix increment. Undefined behavior if we are at end()! - Iter& operator++() noexcept { - mInfo++; - mKeyVals++; - fastForward(); - return *this; - } - - Iter operator++(int) noexcept { - Iter tmp = *this; - ++(*this); - return tmp; - } - - reference operator*() const { - return **mKeyVals; - } - - pointer operator->() const { - return &**mKeyVals; - } - - template - bool operator==(Iter const& o) const noexcept { - return mKeyVals == o.mKeyVals; - } - - template - bool operator!=(Iter const& o) const noexcept { - return mKeyVals != o.mKeyVals; - } - - private: - // fast forward to the next non-free info byte - // I've tried a few variants that don't depend on intrinsics, but unfortunately they are - // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. - void fastForward() noexcept { - size_t n = 0; - while (0U == (n = detail::unaligned_load(mInfo))) { - mInfo += sizeof(size_t); - mKeyVals += sizeof(size_t); - } -#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) - // we know for certain that within the next 8 bytes we'll find a non-zero one. - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 4; - mKeyVals += 4; - } - if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { - mInfo += 2; - mKeyVals += 2; - } - if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { - mInfo += 1; - mKeyVals += 1; - } -#else -# if ROBIN_HOOD(LITTLE_ENDIAN) - auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; -# else - auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; -# endif - mInfo += inc; - mKeyVals += inc; -#endif - } - - friend class Table; - NodePtr mKeyVals{nullptr}; - uint8_t const* mInfo{nullptr}; - }; - - //////////////////////////////////////////////////////////////////// - - // highly performance relevant code. - // Lower bits are used for indexing into the array (2^n size) - // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. - template - void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { - // In addition to whatever hash is used, add another mul & shift so we get better hashing. - // This serves as a bad hash prevention, if the given data is - // badly mixed. - auto h = static_cast(WHash::operator()(key)); - - h *= mHashMultiplier; - h ^= h >> 33U; - - // the lower InitialInfoNumBits are reserved for info. - *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); - *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; - } - - // forwards the index by one, wrapping around at the end - void next(InfoType* info, size_t* idx) const noexcept { - *idx = *idx + 1; - *info += mInfoInc; - } - - void nextWhileLess(InfoType* info, size_t* idx) const noexcept { - // unrolling this by hand did not bring any speedups. - while (*info < mInfo[*idx]) { - next(info, idx); - } - } - - // Shift everything up by one element. Tries to move stuff around. - void - shiftUp(size_t startIdx, - size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { - auto idx = startIdx; - ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); - while (--idx != insertion_idx) { - mKeyVals[idx] = std::move(mKeyVals[idx - 1]); - } - - idx = startIdx; - while (idx != insertion_idx) { - ROBIN_HOOD_COUNT(shiftUp) - mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); - if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - --idx; - } - } - - void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { - // until we find one that is either empty or has zero offset. - // TODO(martinus) we don't need to move everything, just the last one for the same - // bucket. - mKeyVals[idx].destroy(*this); - - // until we find one that is either empty or has zero offset. - while (mInfo[idx + 1] >= 2 * mInfoInc) { - ROBIN_HOOD_COUNT(shiftDown) - mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); - mKeyVals[idx] = std::move(mKeyVals[idx + 1]); - ++idx; - } - - mInfo[idx] = 0; - // don't destroy, we've moved it - // mKeyVals[idx].destroy(*this); - mKeyVals[idx].~Node(); - } - - // copy of find(), except that it returns iterator instead of const_iterator. - template - ROBIN_HOOD(NODISCARD) - size_t findIdx(Other const& key) const { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - do { - // unrolling this twice gives a bit of a speedup. More unrolling did not help. - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - if (info == mInfo[idx] && - ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { - return idx; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found! - return mMask == 0 ? 0 - : static_cast(std::distance( - mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); - } - - void cloneData(const Table& o) { - Cloner()(o, *this); - } - - // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. - // @return True on success, false if something went wrong - void insert_move(Node&& keyval) { - // we don't retry, fail if overflowing - // don't need to check max num elements - if (0 == mMaxNumElementsAllowed && !try_increase_info()) { - throwOverflowError(); - } - - size_t idx{}; - InfoType info{}; - keyToIdx(keyval.getFirst(), &idx, &info); - - // skip forward. Use <= because we are certain that the element is not there. - while (info <= mInfo[idx]) { - idx = idx + 1; - info += mInfoInc; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = static_cast(info); - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - ::new (static_cast(&l)) Node(std::move(keyval)); - } else { - shiftUp(idx, insertion_idx); - l = std::move(keyval); - } - - // put at empty spot - mInfo[insertion_idx] = insertion_info; - - ++mNumElements; - } - -public: - using iterator = Iter; - using const_iterator = Iter; - - Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) - : WHash() - , WKeyEqual() { - ROBIN_HOOD_TRACE(this) - } - - // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. - // This tremendously speeds up ctor & dtor of a map that never receives an element. The - // penalty is payed at the first insert, and not before. Lookup of this empty map works - // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the - // standard, but we can ignore it. - explicit Table( - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - } - - template - Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(first, last); - } - - Table(std::initializer_list initlist, - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : WHash(h) - , WKeyEqual(equal) { - ROBIN_HOOD_TRACE(this) - insert(initlist.begin(), initlist.end()); - } - - Table(Table&& o) noexcept - : WHash(std::move(static_cast(o))) - , WKeyEqual(std::move(static_cast(o))) - , DataPool(std::move(static_cast(o))) { - ROBIN_HOOD_TRACE(this) - if (o.mMask) { - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - // set other's mask to 0 so its destructor won't do anything - o.init(); - } - } - - Table& operator=(Table&& o) noexcept { - ROBIN_HOOD_TRACE(this) - if (&o != this) { - if (o.mMask) { - // only move stuff if the other map actually has some data - destroy(); - mHashMultiplier = std::move(o.mHashMultiplier); - mKeyVals = std::move(o.mKeyVals); - mInfo = std::move(o.mInfo); - mNumElements = std::move(o.mNumElements); - mMask = std::move(o.mMask); - mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); - mInfoInc = std::move(o.mInfoInc); - mInfoHashShift = std::move(o.mInfoHashShift); - WHash::operator=(std::move(static_cast(o))); - WKeyEqual::operator=(std::move(static_cast(o))); - DataPool::operator=(std::move(static_cast(o))); - - o.init(); - - } else { - // nothing in the other map => just clear us. - clear(); - } - } - return *this; - } - - Table(const Table& o) - : WHash(static_cast(o)) - , WKeyEqual(static_cast(o)) - , DataPool(static_cast(o)) { - ROBIN_HOOD_TRACE(this) - if (!o.empty()) { - // not empty: create an exact copy. it is also possible to just iterate through all - // elements and insert them, but copying is probably faster. - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mHashMultiplier = o.mHashMultiplier; - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - // no need for calloc because clonData does memcpy - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - } - } - - // Creates a copy of the given map. Copy constructor of each entry is used. - // Not sure why clang-tidy thinks this doesn't handle self assignment, it does - // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) - Table& operator=(Table const& o) { - ROBIN_HOOD_TRACE(this) - if (&o == this) { - // prevent assigning of itself - return *this; - } - - // we keep using the old allocator and not assign the new one, because we want to keep - // the memory available. when it is the same size. - if (o.empty()) { - if (0 == mMask) { - // nothing to do, we are empty too - return *this; - } - - // not empty: destroy what we have there - // clear also resets mInfo to 0, that's sometimes not necessary. - destroy(); - init(); - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - - return *this; - } - - // clean up old stuff - Destroyer::value>{}.nodes(*this); - - if (mMask != o.mMask) { - // no luck: we don't have the same array size allocated, so we need to realloc. - if (0 != mMask) { - // only deallocate if we actually have data! - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = static_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - - // no need for calloc here because cloneData performs a memcpy. - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - // sentinel is set in cloneData - } - WHash::operator=(static_cast(o)); - WKeyEqual::operator=(static_cast(o)); - DataPool::operator=(static_cast(o)); - mHashMultiplier = o.mHashMultiplier; - mNumElements = o.mNumElements; - mMask = o.mMask; - mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; - mInfoInc = o.mInfoInc; - mInfoHashShift = o.mInfoHashShift; - cloneData(o); - - return *this; - } - - // Swaps everything between the two maps. - void swap(Table& o) { - ROBIN_HOOD_TRACE(this) - using std::swap; - swap(o, *this); - } - - // Clears all data, without resizing. - void clear() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - // don't do anything! also important because we don't want to write to - // DummyInfoByte::b, even though we would just write 0 to it. - return; - } - - Destroyer::value>{}.nodes(*this); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - // clear everything, then set the sentinel again - uint8_t const z = 0; - std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // Destroys the map and all it's contents. - ~Table() { - ROBIN_HOOD_TRACE(this) - destroy(); - } - - // Checks if both tables contain the same entries. Order is irrelevant. - bool operator==(const Table& other) const { - ROBIN_HOOD_TRACE(this) - if (other.size() != size()) { - return false; - } - for (auto const& otherEntry : other) { - if (!has(otherEntry)) { - return false; - } - } - - return true; - } - - bool operator!=(const Table& other) const { - ROBIN_HOOD_TRACE(this) - return !operator==(other); - } - - template - typename std::enable_if::value, Q&>::type operator[](const key_type& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(key), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(key), std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - typename std::enable_if::value, Q&>::type operator[](key_type&& key) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = - Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - } - - return mKeyVals[idxAndState.first].getSecond(); - } - - template - void insert(Iter first, Iter last) { - for (; first != last; ++first) { - // value_type ctor needed because this might be called with std::pair's - insert(value_type(*first)); - } - } - - void insert(std::initializer_list ilist) { - for (auto&& vt : ilist) { - insert(std::move(vt)); - } - } - - template - std::pair emplace(Args&&... args) { - ROBIN_HOOD_TRACE(this) - Node n{*this, std::forward(args)...}; - auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); - switch (idxAndState.second) { - case InsertionState::key_found: - n.destroy(*this); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = std::move(n); - break; - - case InsertionState::overflow_error: - n.destroy(*this); - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - iterator emplace_hint(const_iterator position, Args&&... args) { - (void)position; - return emplace(std::forward(args)...).first; - } - - template - std::pair try_emplace(const key_type& key, Args&&... args) { - return try_emplace_impl(key, std::forward(args)...); - } - - template - std::pair try_emplace(key_type&& key, Args&&... args) { - return try_emplace_impl(std::move(key), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { - (void)hint; - return try_emplace_impl(key, std::forward(args)...).first; - } - - template - iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { - (void)hint; - return try_emplace_impl(std::move(key), std::forward(args)...).first; - } - - template - std::pair insert_or_assign(const key_type& key, Mapped&& obj) { - return insertOrAssignImpl(key, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type&& key, Mapped&& obj) { - return insertOrAssignImpl(std::move(key), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(key, std::forward(obj)).first; - } - - template - iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { - (void)hint; - return insertOrAssignImpl(std::move(key), std::forward(obj)).first; - } - - std::pair insert(const value_type& keyval) { - ROBIN_HOOD_TRACE(this) - return emplace(keyval); - } - - iterator insert(const_iterator hint, const value_type& keyval) { - (void)hint; - return emplace(keyval).first; - } - - std::pair insert(value_type&& keyval) { - return emplace(std::move(keyval)); - } - - iterator insert(const_iterator hint, value_type&& keyval) { - (void)hint; - return emplace(std::move(keyval)).first; - } - - // Returns 1 if key is found, 0 otherwise. - size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type count(const OtherKey& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { - return 1; - } - return 0; - } - - bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - return 1U == count(key); - } - - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::type contains(const OtherKey& key) const { - return 1U == count(key); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q&>::type at(key_type const& key) { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - // Returns a reference to the value found for key. - // Throws std::out_of_range if element cannot be found - template - // NOLINTNEXTLINE(modernize-use-nodiscard) - typename std::enable_if::value, Q const&>::type at(key_type const& key) const { - ROBIN_HOOD_TRACE(this) - auto kv = mKeyVals + findIdx(key); - if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { - doThrow("key not found"); - } - return kv->getSecond(); - } - - const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type // NOLINT(modernize-use-nodiscard) - find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return const_iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator find(const key_type& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - template - typename std::enable_if::type find(const OtherKey& key) { - ROBIN_HOOD_TRACE(this) - const size_t idx = findIdx(key); - return iterator{mKeyVals + idx, mInfo + idx}; - } - - iterator begin() { - ROBIN_HOOD_TRACE(this) - if (empty()) { - return end(); - } - return iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - const_iterator begin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cbegin(); - } - const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - if (empty()) { - return cend(); - } - return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); - } - - iterator end() { - ROBIN_HOOD_TRACE(this) - // no need to supply valid info pointer: end() must not be dereferenced, and only node - // pointer is compared. - return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - const_iterator end() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return cend(); - } - const_iterator cend() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; - } - - iterator erase(const_iterator pos) { - ROBIN_HOOD_TRACE(this) - // its safe to perform const cast here - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); - } - - // Erases element at pos, returns iterator to the next element. - iterator erase(iterator pos) { - ROBIN_HOOD_TRACE(this) - // we assume that pos always points to a valid entry, and not end(). - auto const idx = static_cast(pos.mKeyVals - mKeyVals); - - shiftDown(idx); - --mNumElements; - - if (*pos.mInfo) { - // we've backward shifted, return this again - return pos; - } - - // no backward shift, return next element - return ++pos; - } - - size_t erase(const key_type& key) { - ROBIN_HOOD_TRACE(this) - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - - // check while info matches with the source idx - do { - if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - shiftDown(idx); - --mNumElements; - return 1; - } - next(&info, &idx); - } while (info <= mInfo[idx]); - - // nothing found to delete - return 0; - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // exactly the same as reserve(c). - void rehash(size_t c) { - // forces a reserve - reserve(c, true); - } - - // reserves space for the specified number of elements. Makes sure the old data fits. - // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. - void reserve(size_t c) { - // reserve, but don't force rehash - reserve(c, false); - } - - // If possible reallocates the map to a smaller one. This frees the underlying table. - // Does not do anything if load_factor is too large for decreasing the table's size. - void compact() { - ROBIN_HOOD_TRACE(this) - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (newSize < mMask + 1) { - rehashPowerOfTwo(newSize, true); - } - } - - size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return mNumElements; - } - - size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(-1); - } - - ROBIN_HOOD(NODISCARD) bool empty() const noexcept { - ROBIN_HOOD_TRACE(this) - return 0 == mNumElements; - } - - float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return MaxLoadFactor100 / 100.0F; - } - - // Average number of elements per bucket. Since we allow only 1 per bucket - float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this) - return static_cast(size()) / static_cast(mMask + 1); - } - - ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { - ROBIN_HOOD_TRACE(this) - return mMask; - } - - ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { - if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { - return maxElements * MaxLoadFactor100 / 100; - } - - // we might be a bit inprecise, but since maxElements is quite large that doesn't matter - return (maxElements / 100) * MaxLoadFactor100; - } - - ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { - // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load - // 64bit types. - return numElements + sizeof(uint64_t); - } - - ROBIN_HOOD(NODISCARD) - size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { - auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); - return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); - } - - // calculation only allowed for 2^n values - ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { -#if ROBIN_HOOD(BITNESS) == 64 - return numElements * sizeof(Node) + calcNumBytesInfo(numElements); -#else - // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. - auto const ne = static_cast(numElements); - auto const s = static_cast(sizeof(Node)); - auto const infos = static_cast(calcNumBytesInfo(numElements)); - - auto const total64 = ne * s + infos; - auto const total = static_cast(total64); - - if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { - throwOverflowError(); - } - return total; -#endif - } - -private: - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - auto it = find(e.first); - return it != end() && it->second == e.second; - } - - template - ROBIN_HOOD(NODISCARD) - typename std::enable_if::value, bool>::type has(const value_type& e) const { - ROBIN_HOOD_TRACE(this) - return find(e) != end(); - } - - void reserve(size_t c, bool forceRehash) { - ROBIN_HOOD_TRACE(this) - auto const minElementsAllowed = (std::max)(c, mNumElements); - auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { - newSize *= 2; - } - if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { - throwOverflowError(); - } - - ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") - - // only actually do anything when the new size is bigger than the old one. This prevents to - // continuously allocate for each reserve() call. - if (forceRehash || newSize > mMask + 1) { - rehashPowerOfTwo(newSize, false); - } - } - - // reserves space for at least the specified number of elements. - // only works if numBuckets if power of two - // True on success, false otherwise - void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { - ROBIN_HOOD_TRACE(this) - - Node* const oldKeyVals = mKeyVals; - uint8_t const* const oldInfo = mInfo; - - const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - // resize operation: move stuff - initData(numBuckets); - if (oldMaxElementsWithBuffer > 1) { - for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { - if (oldInfo[i] != 0) { - // might throw an exception, which is really bad since we are in the middle of - // moving stuff. - insert_move(std::move(oldKeyVals[i])); - // destroy the node but DON'T destroy the data. - oldKeyVals[i].~Node(); - } - } - - // this check is not necessary as it's guarded by the previous if, but it helps - // silence g++'s overeager "attempt to free a non-heap object 'map' - // [-Werror=free-nonheap-object]" warning. - if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - // don't destroy old data: put it into the pool instead - if (forceFree) { - std::free(oldKeyVals); - } else { - DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); - } - } - } - } - - ROBIN_HOOD(NOINLINE) void throwOverflowError() const { -#if ROBIN_HOOD(HAS_EXCEPTIONS) - throw std::overflow_error("robin_hood::map overflow"); -#else - abort(); -#endif - } - - template - std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - template - std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { - ROBIN_HOOD_TRACE(this) - auto idxAndState = insertKeyPrepareEmptySpot(key); - switch (idxAndState.second) { - case InsertionState::key_found: - mKeyVals[idxAndState.first].getSecond() = std::forward(obj); - break; - - case InsertionState::new_node: - ::new (static_cast(&mKeyVals[idxAndState.first])) Node( - *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overwrite_node: - mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(obj))); - break; - - case InsertionState::overflow_error: - throwOverflowError(); - break; - } - - return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), - InsertionState::key_found != idxAndState.second); - } - - void initData(size_t max_elements) { - mNumElements = 0; - mMask = max_elements - 1; - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); - - auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - - // malloc & zero mInfo. Faster than calloc everything. - auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); - ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" - << numElementsWithBuffer << ")") - mKeyVals = reinterpret_cast( - detail::assertNotNull(std::malloc(numBytesTotal))); - mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); - std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); - - // set sentinel - mInfo[numElementsWithBuffer] = 1; - - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; - - // Finds key, and if not already present prepares a spot where to pot the key & value. - // This potentially shifts nodes out of the way, updates mInfo and number of inserted - // elements, so the only operation left to do is create/assign a new node at that spot. - template - std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { - for (int i = 0; i < 256; ++i) { - size_t idx{}; - InfoType info{}; - keyToIdx(key, &idx, &info); - nextWhileLess(&info, &idx); - - // while we potentially have a match - while (info == mInfo[idx]) { - if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - // key already exists, do NOT insert. - // see http://en.cppreference.com/w/cpp/container/unordered_map/insert - return std::make_pair(idx, InsertionState::key_found); - } - next(&info, &idx); - } - - // unlikely that this evaluates to true - if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - if (!increase_size()) { - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - continue; - } - - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = info; - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } - - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } - - if (idx != insertion_idx) { - shiftUp(idx, insertion_idx); - } - // put at empty spot - mInfo[insertion_idx] = static_cast(insertion_info); - ++mNumElements; - return std::make_pair(insertion_idx, idx == insertion_idx - ? InsertionState::new_node - : InsertionState::overwrite_node); - } - - // enough attempts failed, so finally give up. - return std::make_pair(size_t(0), InsertionState::overflow_error); - } - - bool try_increase_info() { - ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements - << ", maxNumElementsAllowed=" - << calcMaxNumElementsAllowed(mMask + 1)) - if (mInfoInc <= 2) { - // need to be > 2 so that shift works (otherwise undefined behavior!) - return false; - } - // we got space left, try to make info smaller - mInfoInc = static_cast(mInfoInc >> 1U); - - // remove one bit of the hash, leaving more space for the distance info. - // This is extremely fast because we can operate on 8 bytes at once. - ++mInfoHashShift; - auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - - for (size_t i = 0; i < numElementsWithBuffer; i += 8) { - auto val = unaligned_load(mInfo + i); - val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); - std::memcpy(mInfo + i, &val, sizeof(val)); - } - // update sentinel, which might have been cleared out! - mInfo[numElementsWithBuffer] = 1; - - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - return true; - } - - // True if resize was possible, false otherwise - bool increase_size() { - // nothing allocated yet? just allocate InitialNumElements - if (0 == mMask) { - initData(InitialNumElements); - return true; - } - - auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); - if (mNumElements < maxNumElementsAllowed && try_increase_info()) { - return true; - } - - ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" - << maxNumElementsAllowed << ", load=" - << (static_cast(mNumElements) * 100.0 / - (static_cast(mMask) + 1))) - - if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { - // we have to resize, even though there would still be plenty of space left! - // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case - // we have to rehash a few times - nextHashMultiplier(); - rehashPowerOfTwo(mMask + 1, true); - } else { - // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. - rehashPowerOfTwo((mMask + 1) * 2, false); - } - return true; - } - - void nextHashMultiplier() { - // adding an *even* number, so that the multiplier will always stay odd. This is necessary - // so that the hash stays a mixing function (and thus doesn't have any information loss). - mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); - } - - void destroy() { - if (0 == mMask) { - // don't deallocate! - return; - } - - Destroyer::value>{} - .nodesDoNotDeallocate(*this); - - // This protection against not deleting mMask shouldn't be needed as it's sufficiently - // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise - // reports a compile error: attempt to free a non-heap object 'fm' - // [-Werror=free-nonheap-object] - if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { - ROBIN_HOOD_LOG("std::free") - std::free(mKeyVals); - } - } - - void init() noexcept { - mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); - mInfo = reinterpret_cast(&mMask); - mNumElements = 0; - mMask = 0; - mMaxNumElementsAllowed = 0; - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; - } - - // members are sorted so no padding occurs - uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 - Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 - uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 - size_t mNumElements = 0; // 8 byte 32 - size_t mMask = 0; // 8 byte 40 - size_t mMaxNumElementsAllowed = 0; // 8 byte 48 - InfoType mInfoInc = InitialInfoInc; // 4 byte 52 - InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 - // 16 byte 56 if NodeAllocator -}; - -} // namespace detail - -// map - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_flat_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_node_map = detail::Table; - -template , - typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_map = - detail::Table) <= sizeof(size_t) * 6 && - std::is_nothrow_move_constructible>::value && - std::is_nothrow_move_assignable>::value, - MaxLoadFactor100, Key, T, Hash, KeyEqual>; - -// set - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_flat_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_node_set = detail::Table; - -template , typename KeyEqual = std::equal_to, - size_t MaxLoadFactor100 = 80> -using unordered_set = detail::Table::value && - std::is_nothrow_move_assignable::value, - MaxLoadFactor100, Key, void, Hash, KeyEqual>; - -} // namespace robin_hood - -#endif diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 16978fe..f6b2729 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -37,6 +37,7 @@ apps: - network-observe - home - removable-media + - opengl parts: btop: @@ -47,7 +48,6 @@ parts: - PREFIX=/usr/local - STATIC=true - ADDFLAGS="-D SNAPPED" - build-packages: - coreutils - sed diff --git a/src/btop.cpp b/src/btop.cpp index 9a07486..48d0482 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -32,6 +32,7 @@ tab-size = 4 #include #include #include +#include #ifdef __APPLE__ #include #include @@ -75,7 +76,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.2.13"; + const string Version = "1.3.0"; int coreCount; string overlay; @@ -183,8 +184,11 @@ void term_resize(bool force) { if (force and refreshed) force = false; } else return; - - static const array all_boxes = {"cpu", "mem", "net", "proc"}; +#ifdef GPU_SUPPORT + static const array all_boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"}; +#else + static const array all_boxes = {"", "cpu", "mem", "net", "proc"}; +#endif Global::resized = true; if (Runner::active) Runner::stop(); Term::refresh(); @@ -222,10 +226,18 @@ void term_resize(bool force) { auto key = Input::get(); if (key == "q") clean_quit(0); - else if (is_in(key, "1", "2", "3", "4")) { - Config::current_preset = -1; - Config::toggle_box(all_boxes.at(std::stoi(key) - 1)); - boxes = Config::getS("shown_boxes"); + else if (key.size() == 1 and isint(key)) { + auto intKey = stoi(key); + #ifdef GPU_SUPPORT + if ((intKey == 0 and Gpu::gpu_names.size() >= 5) or (intKey >= 5 and std::cmp_greater_equal(Gpu::gpu_names.size(), intKey - 4))) { + #else + if (intKey > 0 and intKey < 5) { + #endif + 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); @@ -258,6 +270,11 @@ void clean_quit(int sig) { #endif } +#ifdef GPU_SUPPORT + Gpu::Nvml::shutdown(); + Gpu::Rsmi::shutdown(); +#endif + Config::write(); if (Term::initialized) { @@ -388,7 +405,9 @@ namespace Runner { enum debug_actions { collect_begin, + collect_done, draw_begin, + draw_begin_only, draw_done }; @@ -398,7 +417,7 @@ namespace Runner { }; string debug_bg; - unordered_flat_map> debug_times; + std::unordered_map> debug_times; class MyNumPunct : public std::numpunct { @@ -424,6 +443,13 @@ namespace Runner { 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); @@ -480,10 +506,14 @@ namespace Runner { //! DEBUG stats if (Global::debug) { - if (debug_bg.empty() or redraw) - Runner::debug_bg = Draw::createBox(2, 2, 33, 8, "", true, "μs"); - - + 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}; @@ -493,6 +523,29 @@ namespace Runner { //* 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_upper").starts_with("gpu-") + or (Gpu::shown == 0 and Config::getS("show_gpu_info") != "Off") + ); + + vector gpu_panels = {}; + for (auto& box : conf.boxes) + if (box.starts_with("gpu")) + gpu_panels.push_back(box.back()-'0'); + + vector 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 gpus_ref{}; + #endif + //? CPU if (v_contains(conf.boxes, "cpu")) { try { @@ -512,7 +565,7 @@ namespace Runner { if (Global::debug) debug_timer("cpu", draw_begin); //? Draw box - if (not pause_output) output += Cpu::draw(cpu, conf.force_redraw, conf.no_update); + if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("cpu", draw_done); } @@ -520,7 +573,24 @@ namespace Runner { 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 { @@ -580,6 +650,7 @@ namespace Runner { throw std::runtime_error("Proc:: -> " + string{e.what()}); } } + } catch (const std::exception& e) { Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()}; @@ -610,8 +681,9 @@ namespace Runner { "{mv3}{hiFg}2 {mainFg}| Show MEM box" "{mv4}{hiFg}3 {mainFg}| Show NET box" "{mv5}{hiFg}4 {mainFg}| Show PROC box" - "{mv6}{hiFg}esc {mainFg}| Show menu" - "{mv7}{hiFg}q {mainFg}| Quit", + "{mv6}{hiFg}5-0 {mainFg}| Show GPU boxes" + "{mv7}{hiFg}esc {mainFg}| Show menu" + "{mv8}{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), @@ -620,7 +692,8 @@ namespace Runner { "mv4"_a = Mv::to(y+10, x), "mv5"_a = Mv::to(y+11, x), "mv6"_a = Mv::to(y+12, x-2), - "mv7"_a = Mv::to(y+13, x) + "mv7"_a = Mv::to(y+13, x-2), + "mv8"_a = Mv::to(y+14, x) ); } output += empty_bg; @@ -634,7 +707,11 @@ namespace Runner { "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; diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6010189..6ddfd43 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,6 +21,7 @@ tab-size = 4 #include #include #include +#include #include @@ -73,14 +74,16 @@ namespace Config { "#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."}, {"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, - +#ifdef GPU_SUPPORT + {"graph_symbol_gpu", "# Graph symbol to use for graphs in gpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, +#endif {"graph_symbol_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, {"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, {"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, - {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."}, + {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\" and \"gpu0\" through \"gpu5\", separate values with whitespace."}, {"update_ms", "#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs."}, @@ -107,12 +110,16 @@ namespace Config { {"proc_filter_kernel", "#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop)."}, + {"proc_aggregate", "#* In tree-view, always accumulate child process resources in the parent process."}, + {"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n" "#* Select from a list of detected attributes from the options menu."}, {"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n" "#* Select from a list of detected attributes from the options menu."}, - + #ifdef GPU_SUPPORT + {"show_gpu_info", "#* If gpu info should be shown in the cpu box. Available values = \"Auto\", \"On\" and \"Off\"."}, + #endif {"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."}, {"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."}, @@ -192,21 +199,36 @@ namespace Config { {"selected_battery", "#* Which battery to use if multiple are present. \"Auto\" for auto detection."}, {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" - "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."} + "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}, + #ifdef GPU_SUPPORT + + {"nvml_measure_pcie_speeds", + "#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards."}, + + {"gpu_mirror_graph", "#* Horizontally mirror the GPU graph."}, + + {"custom_gpu_name0", "#* Custom gpu0 model name, empty string to disable."}, + {"custom_gpu_name1", "#* Custom gpu1 model name, empty string to disable."}, + {"custom_gpu_name2", "#* Custom gpu2 model name, empty string to disable."}, + {"custom_gpu_name3", "#* Custom gpu3 model name, empty string to disable."}, + {"custom_gpu_name4", "#* Custom gpu4 model name, empty string to disable."}, + {"custom_gpu_name5", "#* Custom gpu5 model name, empty string to disable."}, + #endif }; - unordered_flat_map strings = { + std::unordered_map strings = { {"color_theme", "Default"}, {"shown_boxes", "cpu mem net proc"}, {"graph_symbol", "braille"}, {"presets", "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"}, {"graph_symbol_cpu", "default"}, + {"graph_symbol_gpu", "default"}, {"graph_symbol_mem", "default"}, {"graph_symbol_net", "default"}, {"graph_symbol_proc", "default"}, {"proc_sorting", "cpu lazy"}, - {"cpu_graph_upper", "total"}, - {"cpu_graph_lower", "total"}, + {"cpu_graph_upper", "Auto"}, + {"cpu_graph_lower", "Auto"}, {"cpu_sensor", "Auto"}, {"selected_battery", "Auto"}, {"cpu_core_map", ""}, @@ -220,10 +242,19 @@ namespace Config { {"proc_filter", ""}, {"proc_command", ""}, {"selected_name", ""}, + #ifdef GPU_SUPPORT + {"custom_gpu_name0", ""}, + {"custom_gpu_name1", ""}, + {"custom_gpu_name2", ""}, + {"custom_gpu_name3", ""}, + {"custom_gpu_name4", ""}, + {"custom_gpu_name5", ""}, + {"show_gpu_info", "Auto"} + #endif }; - unordered_flat_map stringsTmp; + std::unordered_map stringsTmp; - unordered_flat_map bools = { + std::unordered_map bools = { {"theme_background", true}, {"truecolor", true}, {"rounded_corners", true}, @@ -268,10 +299,15 @@ namespace Config { {"lowcolor", false}, {"show_detailed", false}, {"proc_filtering", false}, + {"proc_aggregate", false}, + #ifdef GPU_SUPPORT + {"nvml_measure_pcie_speeds", true}, + {"gpu_mirror_graph", true}, + #endif }; - unordered_flat_map boolsTmp; + std::unordered_map boolsTmp; - unordered_flat_map ints = { + std::unordered_map ints = { {"update_ms", 2000}, {"net_download", 100}, {"net_upload", 100}, @@ -282,7 +318,7 @@ namespace Config { {"proc_selected", 0}, {"proc_last_selected", 0}, }; - unordered_flat_map intsTmp; + std::unordered_map intsTmp; bool _locked(const std::string_view name) { atomic_wait(writelock, true); @@ -318,7 +354,7 @@ namespace Config { validError = "Malformatted preset in config value presets!"; return false; } - if (not is_in(vals.at(0), "cpu", "mem", "net", "proc")) { + if (not is_in(vals.at(0), "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5")) { validError = "Invalid box name in config value presets!"; return false; } @@ -414,6 +450,11 @@ namespace Config { else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) validError = "Invalid box name(s) in shown_boxes!"; + #ifdef GPU_SUPPORT + else if (name == "show_gpu_info" and not v_contains(show_gpu_values, value)) + validError = "Invalid value for show_gpu_info: " + value; + #endif + else if (name == "presets" and not presetsValid(value)) return false; @@ -516,6 +557,13 @@ namespace Config { auto new_boxes = ssplit(boxes); for (auto& box : new_boxes) { if (not v_contains(valid_boxes, box)) return false; + #ifdef GPU_SUPPORT + if (box.starts_with("gpu")) { + size_t gpu_num = stoi(box.substr(3)); + if (gpu_num == 0) gpu_num = 5; + if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false; + } + #endif } current_boxes = std::move(new_boxes); return true; diff --git a/src/btop_config.hpp b/src/btop_config.hpp index e5f4ac1..2b586af 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -22,11 +22,10 @@ tab-size = 4 #include #include -#include +#include using std::string; using std::vector; -using robin_hood::unordered_flat_map; //* Functions and variables for reading and writing the btop config file namespace Config { @@ -34,18 +33,25 @@ namespace Config { extern std::filesystem::path conf_dir; extern std::filesystem::path conf_file; - extern unordered_flat_map strings; - extern unordered_flat_map stringsTmp; - extern unordered_flat_map bools; - extern unordered_flat_map boolsTmp; - extern unordered_flat_map ints; - extern unordered_flat_map intsTmp; + extern std::unordered_map strings; + extern std::unordered_map stringsTmp; + extern std::unordered_map bools; + extern std::unordered_map boolsTmp; + extern std::unordered_map ints; + extern std::unordered_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; - const vector valid_boxes = { "cpu", "mem", "net", "proc" }; + const vector valid_boxes = { + "cpu", "mem", "net", "proc" +#ifdef GPU_SUPPORT + ,"gpu0", "gpu1", "gpu2", "gpu3", "gpu4", "gpu5" +#endif + }; const vector temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; - +#ifdef GPU_SUPPORT + const vector show_gpu_values = { "Auto", "On", "Off" }; +#endif extern vector current_boxes; extern vector preset_list; extern vector available_batteries; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index a4cbb95..0ceaa2f 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -20,6 +20,9 @@ tab-size = 4 #include #include #include +#include +#include +#include #include "btop_draw.hpp" #include "btop_config.hpp" @@ -29,6 +32,7 @@ tab-size = 4 #include "btop_input.hpp" #include "btop_menu.hpp" + using std::array; using std::clamp; using std::cmp_equal; @@ -51,7 +55,7 @@ namespace Symbols { const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; - const unordered_flat_map> graph_symbols = { + const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", @@ -297,7 +301,7 @@ namespace Draw { return false; } - static const unordered_flat_map clock_custom_format = { + static const std::unordered_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} @@ -508,44 +512,69 @@ namespace Cpu { int x = 1, y = 1, width = 20, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; - int graph_up_height; long unsigned int lavg_str_len = 0; + int graph_up_height, graph_low_height; + int graph_up_width, graph_low_width; + int gpu_meter_width; bool shown = true, redraw = true, mid_line = false; string box; - Draw::Graph graph_upper; - Draw::Graph graph_lower; + vector graphs_upper; + vector graphs_lower; Draw::Meter cpu_meter; + vector gpu_meters; vector core_graphs; vector temp_graphs; + vector gpu_temp_graphs; + vector gpu_mem_graphs; - string draw(const cpu_info& cpu, bool force_redraw, bool data_same) { + string draw(const cpu_info& cpu, const vector& gpus, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; bool show_temps = (Config::getB("check_temp") and got_sensors); auto single_graph = Config::getB("cpu_single_graph"); bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp")); const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0); - auto& graph_up_field = Config::getS("cpu_graph_upper"); - auto& graph_lo_field = Config::getS("cpu_graph_lower"); + #ifdef GPU_SUPPORT + const auto& show_gpu_info = Config::getS("show_gpu_info"); + const bool gpu_always = show_gpu_info == "On"; + bool show_gpu = (gpus.size() > 0 and (gpu_always or (show_gpu_info == "Auto" and Gpu::shown == 0))); + #else + (void)gpus; + #endif + auto graph_up_field = Config::getS("cpu_graph_upper"); + if (graph_up_field == "Auto" or not v_contains(Cpu::available_fields, graph_up_field)) + graph_up_field = "total"; + auto graph_lo_field = Config::getS("cpu_graph_lower"); + if (graph_lo_field == "Auto" or not v_contains(Cpu::available_fields, graph_lo_field)) { + #ifdef GPU_SUPPORT + graph_lo_field = show_gpu ? "gpu-totals" : graph_up_field; + #else + graph_lo_field = graph_up_field; + #endif + } auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& temp_scale = Config::getS("temp_scale"); auto cpu_bottom = Config::getB("cpu_bottom"); + const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; - if (cpu.cpu_percent.at("total").empty() - or cpu.core_percent.at(0).empty() - or (show_temps and cpu.temp.at(0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; + if (safeVal(cpu.cpu_percent, "total"s).empty() + or safeVal(cpu.core_percent, 0).empty() + or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle if (redraw) { mid_line = (not single_graph and graph_up_field != graph_lo_field); - graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0 ? 1 : 0)); - const int graph_low_height = height - 2 - graph_up_height - (mid_line ? 1 : 0); + graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0)); + graph_low_height = height - 2 - graph_up_height - mid_line; const int button_y = cpu_bottom ? y + height - 1 : y; out += box; @@ -562,17 +591,91 @@ namespace Cpu { Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2}; //? Graphs & meters - graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", cpu.cpu_percent.at(graph_up_field), graph_symbol, false, true}; + const int graph_default_width = x + width - b_width - 3; + + auto init_graphs = [&](vector& graphs, const int graph_height, int& graph_width, const string& graph_field, bool invert) { + #ifdef GPU_SUPPORT + if (graph_field.starts_with("gpu")) { + if (graph_field.find("totals") != string::npos) { + graphs.resize(gpus.size()); + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + graph_width = graph_default_width/(int)gpus.size() - (int)gpus.size() + 1 + graph_default_width%gpus.size(); + for (unsigned long i = 0;;) { + auto& gpu = gpus[i]; auto& graph = graphs[i]; + + //? GPU graphs/meters + if (gpu.supported_functions.temp_info) + gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; + if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol }; + if (gpu.supported_functions.gpu_utilization) { + gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpu.mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); + gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; + } + + bool utilization_support = gpu.supported_functions.gpu_utilization; + if (++i < gpus.size()) { + if (utilization_support) + graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true}; + } else { + if (utilization_support) + graph = Draw::Graph{ + graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, + graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true + }; + break; + } + } + } else { + graphs.resize(1); + graph_width = graph_default_width; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true }; + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + for (unsigned long i = 0; i < gpus.size(); ++i) { + if (gpus[i].supported_functions.temp_info) + gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; + if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; + if (gpus[i].supported_functions.gpu_utilization) { + gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); + gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; + } + } + } + } else { + #endif + graphs.resize(1); + graph_width = graph_default_width; + graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true }; + #ifdef GPU_SUPPORT + if (std::cmp_less(Gpu::shown, gpus.size())) { + gpu_temp_graphs.resize(gpus.size()); + gpu_mem_graphs.resize(gpus.size()); + gpu_meters.resize(gpus.size()); + for (unsigned long i = 0; i < gpus.size(); ++i) { + if (gpus[i].supported_functions.temp_info) + gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpus[i].temp, graph_symbol, false, false, gpus[i].temp_max, -23 }; + if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total) + gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), graph_symbol }; + if (gpus[i].supported_functions.gpu_utilization) { + gpu_meter_width = b_width - 12 - (int)floating_humanizer(gpus[i].mem_total, true).size() - (show_temps ? 24 : 12) - (int)to_string(i).size() + (gpus.size() == 1)*2 - (gpus.size() > 9 and i <= 9); + gpu_meters[i] = Draw::Meter{gpu_meter_width, "cpu" }; + } + } + } + } + #endif + }; + + init_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field, false); + if (not single_graph) + init_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field, Config::getB("cpu_invert_lower")); + cpu_meter = Draw::Meter{b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11), "cpu"}; - if (not single_graph) { - graph_lower = Draw::Graph{ - x + width - b_width - 3, - graph_low_height, "cpu", - cpu.cpu_percent.at(graph_lo_field), - graph_symbol, - Config::getB("cpu_invert_lower"), true - }; - } if (mid_line) { out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line") @@ -590,10 +693,10 @@ namespace Cpu { if (show_temps) { temp_graphs.clear(); - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(0), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { - temp_graphs.emplace_back(5, 1, "temp", cpu.temp.at(i), graph_symbol, false, false, cpu.temp_max, -23); + temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23); } } } @@ -605,7 +708,7 @@ namespace Cpu { static long old_seconds{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; - static const unordered_flat_map bat_symbols = { + static const std::unordered_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, @@ -640,10 +743,39 @@ namespace Cpu { } try { - //? Cpu graphs - out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw)); - if (not single_graph) - out += Mv::to( y + graph_up_height + 1 + (mid_line ? 1 : 0), x + 1) + graph_lower(cpu.cpu_percent.at(graph_lo_field), (data_same or redraw)); + //? Cpu/Gpu graphs + out += Fx::ub + Mv::to(y + 1, x + 1); + auto draw_graphs = [&](vector& graphs, const int graph_height, const int graph_width, const string& graph_field) { + #ifdef GPU_SUPPORT + if (graph_field.starts_with("gpu")) + if (graph_field.find("totals") != string::npos) + for (unsigned long i = 0;;) { + out += graphs[i](safeVal(gpus[i].gpu_percent, graph_field), (data_same or redraw)); + if (gpus.size() > 1) { + auto i_str = to_string(i); + out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU " : "") + i_str + + Mv::d(graph_height/2) + Mv::r(graph_width - 1 - (graph_width > 5)*4 - i_str.size()); + } + + if (++i < graphs.size()) + out += Theme::c("div_line") + (Symbols::v_line + Mv::l(1) + Mv::u(1))*graph_height + Mv::r(1) + Mv::d(1); + else break; + } + else + out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw)); + else + #else + (void)graph_height; + (void)graph_width; + #endif + out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); + }; + + draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); + if (not single_graph) { + out += Mv::to(y + graph_up_height + 1 + mid_line, x + 1); + draw_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field); + } //? Uptime if (Config::getB("show_uptime")) { @@ -661,14 +793,14 @@ namespace Cpu { out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; - out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) - + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) + + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { - const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); - if (b_column_size > 1 or b_columns > 1) + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); + if ((b_column_size > 1 or b_columns > 1) and temp_graphs.size() >= 1ll) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color - + temp_graphs.at(0)(cpu.temp.at(0), data_same or redraw); + + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; @@ -679,21 +811,22 @@ namespace Cpu { int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); if (Shared::coreCount >= 100) core_width++; for (const auto& n : iota(0, Shared::coreCount)) { + if (cmp_less(core_graphs.size(), n+1)) break; out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + + core_graphs.at(n)(safeVal(cpu.core_percent, n), data_same or redraw); - out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); - out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; + out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); + out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; - if (show_temps and not hide_cores) { - const auto [temp, unit] = celsius_to(cpu.temp.at(n+1).back(), temp_scale); - const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); + if (show_temps and not hide_cores and std::cmp_greater_equal(temp_graphs.size(), n)) { + const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); + const auto& temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) - + temp_graphs.at(n+1)(cpu.temp.at(n+1), data_same or redraw); + + temp_graphs.at(n+1)(safeVal(cpu.temp, n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } @@ -731,15 +864,231 @@ namespace Cpu { } else { lavg_str_len = lavg_str.length(); } - out += Mv::to(b_y + b_height - 2, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; + #ifdef GPU_SUPPORT + cy = b_height - 2 - (show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0); + #else + cy = b_height - 2; + #endif + out += Mv::to(b_y + cy, b_x + cx + 1) + Theme::c("main_fg") + lavg_str; } + #ifdef GPU_SUPPORT + //? Gpu brief info + if (show_gpu) { + for (unsigned long i = 0; i < gpus.size(); ++i) { + if (gpu_always or not v_contains(Gpu::shown_panels, i)) { + out += Mv::to(b_y + ++cy, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU"; + if (show_temps and gpus[i].supported_functions.temp_info and b_width < 34) { + const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); + if (temp < 100) out += " "; + } + if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); + if (gpus[i].supported_functions.gpu_utilization) { + string meter = gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()); + out += (meter.size() > 1 ? " " : "") + meter + + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; + } else out += Mv::r(gpu_meter_width); + + if (gpus[i].supported_functions.mem_used) { + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back()) + + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw) + Theme::c("main_fg") + + rjust(floating_humanizer(gpus[i].mem_used, true), 5); + if (gpus[i].supported_functions.mem_total) + out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + floating_humanizer(gpus[i].mem_total, true); + else out += Mv::r(5); + } else out += Mv::r(17); + if (show_temps and gpus[i].supported_functions.temp_info) { + const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); + if (b_width > 38) + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)) + + gpu_temp_graphs[i](gpus[i].temp, data_same or redraw); + else out += Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)); + out += rjust(to_string(temp), 3 + (b_width >= 34 or temp > 99)) + Theme::c("main_fg") + unit; + } + } + if (cy < b_height - 1) break; + } + } + #endif + redraw = false; return out + Fx::reset; } } +#ifdef GPU_SUPPORT +namespace Gpu { + int width_p = 100, height_p = 32; + int min_width = 41, min_height = 11; + int width = 41, height; + vector x_vec = {}, y_vec = {}, b_height_vec = {}; + int b_width; + vector b_x_vec = {}, b_y_vec = {}; + vector redraw = {}; + int shown = 0; + vector shown_panels = {}; + int graph_up_height; + vector graph_upper_vec = {}, graph_lower_vec = {}; + vector temp_graph_vec = {}; + vector mem_used_graph_vec = {}, mem_util_graph_vec = {}; + vector gpu_meter_vec = {}; + vector pwr_meter_vec = {}; + vector box = {}; + + string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same) { + if (Runner::stopping) return ""; + + auto& b_x = b_x_vec[index]; + auto& b_y = b_y_vec[index]; + auto& x = x_vec[index]; + auto& y = y_vec[index]; + + auto& graph_upper = graph_upper_vec[index]; + auto& graph_lower = graph_lower_vec[index]; + auto& temp_graph = temp_graph_vec[index]; + auto& mem_used_graph = mem_used_graph_vec[index]; + auto& mem_util_graph = mem_util_graph_vec[index]; + auto& gpu_meter = gpu_meter_vec[index]; + auto& pwr_meter = pwr_meter_vec[index]; + + if (force_redraw) redraw[index] = true; + bool show_temps = gpu.supported_functions.temp_info and (Config::getB("check_temp")); + auto tty_mode = Config::getB("tty_mode"); + auto& temp_scale = Config::getS("temp_scale"); + auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_gpu")); + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); + auto single_graph = !Config::getB("gpu_mirror_graph"); + string out; + out.reserve(width * height); + + //* Redraw elements not needed to be updated every cycle + if (redraw[index]) { + graph_up_height = single_graph ? height - 2 : ceil((double)(height - 2) / 2); + const int graph_low_height = height - 2 - graph_up_height; + out += box[index]; + + if (gpu.supported_functions.gpu_utilization) { + graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu + if (not single_graph) { + graph_lower = Draw::Graph{ + x + width - b_width - 3, + graph_low_height, "cpu", + safeVal(gpu.gpu_percent, "gpu-totals"s), + graph_symbol, + Config::getB("cpu_invert_lower"), true + }; + } + gpu_meter = Draw::Meter{b_width - (show_temps ? 24 : 11), "cpu"}; + } + if (gpu.supported_functions.temp_info) + temp_graph = Draw::Graph{6, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23}; + if (gpu.supported_functions.pwr_usage) + pwr_meter = Draw::Meter{b_width - 24, "cached"}; + if (gpu.supported_functions.mem_utilization) + mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization + if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) + mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; + } + + + //* General GPU info + + //? Gpu graph, meter & clock speed + if (gpu.supported_functions.gpu_utilization) { + out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); + if (not single_graph) + out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); + + out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back()) + + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 4) + Theme::c("main_fg") + '%'; + + //? Temperature graph, I assume the device supports utilization if it supports temperature + if (show_temps) { + const auto [temp, unit] = celsius_to(gpu.temp.back(), temp_scale); + out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll)) + + temp_graph(gpu.temp, data_same or redraw[index]); + out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; + } + out += Theme::c("div_line") + Symbols::v_line; + } + + if (gpu.supported_functions.gpu_clock) { + string clock_speed_string = to_string(gpu.gpu_clock_speed); + out += Mv::to(b_y, b_x + b_width - 12) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; + } + + //? Power usage meter, power state + if (gpu.supported_functions.pwr_usage) { + out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back()) + + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + rjust(to_string(gpu.pwr_usage/1000), 4) + Theme::c("main_fg") + 'W'; + if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card + out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); + } + + if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { + out += Mv::to(b_y + 3, b_x); + if (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) { + string used_memory_string = floating_humanizer(gpu.mem_used); + + auto offset = (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) + * (1 + 2*(gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) + 2*gpu.supported_functions.mem_utilization); + + //? Used graph, memory section header, total vram + out += Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Fx::b + Theme::c("title") + "vram" + Theme::c("div_line") + Fx::ub + Symbols::title_right + + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right + + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index])) + + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub + + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; + + //? Memory utilization + if (gpu.supported_functions.mem_utilization) + out += Mv::l(b_width/2+6) + Mv::d(1) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "Utilization:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-14) + Symbols::div_right + + Mv::l(b_width/2) + Mv::d(1) + mem_util_graph(gpu.mem_utilization_percent, (data_same or redraw[index])) + + Mv::l(b_width/2-1) + Mv::u(1) + rjust(to_string(gpu.mem_utilization_percent.back()), 3) + '%'; + + //? Memory clock speed + if (gpu.supported_functions.mem_clock) { + string clock_speed_string = to_string(gpu.mem_clock_speed); + out += Mv::to(b_y + 3, b_x + b_width/2 - 11) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " Mhz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; + } + } else { + out += Theme::c("main_fg") + Mv::r(1); + if (gpu.supported_functions.mem_total) + out += "VRAM total:" + rjust(floating_humanizer(gpu.mem_total), b_width/(1 + gpu.supported_functions.mem_clock)-14); + else out += "VRAM usage:" + rjust(floating_humanizer(gpu.mem_used), b_width/(1 + gpu.supported_functions.mem_clock)-14); + + if (gpu.supported_functions.mem_clock) + out += " VRAM clock:" + rjust(to_string(gpu.mem_clock_speed) + " Mhz", b_width/2-13); + } + } + + //? Processes section header + //out += Mv::to(b_y+8, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Theme::c("main_fg") + Fx::b + "gpu-proc" + Fx::ub + Theme::c("div_line") + // + Symbols::title_right + Symbols::h_line*(b_width/2-12) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right; + + //? PCIe link throughput + if (gpu.supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) { + string tx_string = floating_humanizer(gpu.pcie_tx, 0, 1, 0, 1); + string rx_string = floating_humanizer(gpu.pcie_rx, 0, 1, 0, 1); + out += Mv::to(b_y + b_height_vec[index] - 1, b_x+2) + Theme::c("div_line") + + Symbols::title_left_down + Theme::c("title") + Fx::b + "TX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2-9-tx_string.size()) + + Symbols::title_left_down + Theme::c("title") + Fx::b + tx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used ? Symbols::div_down : Symbols::h_line) + + Symbols::title_left_down + Theme::c("title") + Fx::b + "RX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2+b_width%2-9-rx_string.size()) + + Symbols::title_left_down + Theme::c("title") + Fx::b + rx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::round_right_down; + } + + redraw[index] = false; + return out + Fx::reset; + } + +} +#endif + namespace Mem { int width_p = 45, height_p = 36; int min_width = 36, min_height = 10; @@ -749,11 +1098,11 @@ namespace Mem { int disks_io_half = 0; bool shown = true, redraw = true; string box; - unordered_flat_map mem_meters; - unordered_flat_map mem_graphs; - unordered_flat_map disk_meters_used; - unordered_flat_map disk_meters_free; - unordered_flat_map io_graphs; + std::unordered_map mem_meters; + std::unordered_map mem_graphs; + std::unordered_map disk_meters_used; + std::unordered_map disk_meters_free; + std::unordered_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; @@ -785,14 +1134,14 @@ namespace Mem { for (const auto& name : mem_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) - mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), mem.percent.at(name), graph_symbol}; + mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } @@ -801,7 +1150,7 @@ namespace Mem { //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { - unordered_flat_map custom_speeds; + std::unordered_map custom_speeds; int half_height = 0; if (io_mode) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); @@ -883,7 +1232,7 @@ namespace Mem { if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; cy += 1; } - out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(mem.stats.at("swap_total")), mem_width - 8) + out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8) + Theme::c("main_fg") + Fx::ub; cy += 1; title = "Used"; @@ -892,13 +1241,16 @@ namespace Mem { title = "Free"; if (title.empty()) title = capitalize(name); - const string humanized = floating_humanizer(mem.stats.at(name)); + const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); - const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); + const string graphics = ( + use_graphs and mem_graphs.contains(name) ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same) + : mem_meters.contains(name) ? mem_meters.at(name)(safeVal(mem.percent, name).back()) + : ""); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) - + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); + + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4); cy += (graph_height == 0 ? 2 : graph_height + 1); } else { @@ -921,7 +1273,7 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); if (disk.io_read.empty()) continue; const string total = floating_humanizer(disk.total, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) @@ -930,9 +1282,12 @@ namespace Mem { const string used_percent = to_string(disk.used_percent); out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } + if (io_graphs.contains(mount + "_activity")) { out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); + } if (++cy > height - 3) break; + if (not io_graphs.contains(mount)) continue; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) @@ -957,7 +1312,8 @@ namespace Mem { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; - const auto& disk = disks.at(mount); + const auto& disk = safeVal(disks, mount); + if (disk.name.empty() or not disk_meters_used.contains(mount)) continue; auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); @@ -981,7 +1337,7 @@ namespace Mem { + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; - if (cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { + if (disk_meters_free.contains(mount) and cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; @@ -1006,7 +1362,7 @@ namespace Net { int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; string old_ip; - unordered_flat_map graphs; + std::unordered_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { @@ -1026,15 +1382,15 @@ namespace Net { const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const int i_size = min((int)selected_iface.size(), 10); - const long long down_max = (net_auto ? graph_max.at("download") : ((long long)(Config::getI("net_download")) << 20) / 8); - const long long up_max = (net_auto ? graph_max.at("upload") : ((long long)(Config::getI("net_upload")) << 20) / 8); + const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); + const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs graphs.clear(); - if (net.bandwidth.at("download").empty() or net.bandwidth.at("upload").empty()) + if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty()) return out + Fx::reset; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", @@ -1048,7 +1404,7 @@ namespace Net { out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right - + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (net.stat.at("download").offset + net.stat.at("upload").offset > 0 ? Fx::b : "") + 'z' + + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; @@ -1072,13 +1428,13 @@ namespace Net { //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { - out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(net.bandwidth.at(dir), redraw or data_same or not net.connected) + out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); - const string speed = floating_humanizer(net.stat.at(dir).speed, false, 0, false, true); - const string speed_bits = (b_width >= 20 ? floating_humanizer(net.stat.at(dir).speed, false, 0, true, true) : ""); - const string top = floating_humanizer(net.stat.at(dir).top, false, 0, true, true); - const string total = floating_humanizer(net.stat.at(dir).total); + const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); + const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); + const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); + const string total = floating_humanizer(safeVal(net.stat, dir).total); const string symbol = (dir == "upload" ? "▲" : "▼"); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); cy += (b_height == 5 ? 2 : 1); @@ -1106,9 +1462,9 @@ namespace Proc { bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; - unordered_flat_map p_graphs; - unordered_flat_map p_wide_cmd; - unordered_flat_map p_counters; + std::unordered_map p_graphs; + std::unordered_map p_wide_cmd; + std::unordered_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; @@ -1443,11 +1799,11 @@ namespace Proc { p_counters[p.pid] = 0; } else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) { - p_graphs.erase(p.pid); + if (p_graphs.contains(p.pid)) p_graphs.erase(p.pid); p_counters.erase(p.pid); } else - p_counters.at(p.pid) = 0; + p_counters[p.pid] = 0; } out += Fx::reset; @@ -1529,7 +1885,17 @@ namespace Proc { else mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); mem_str += '%'; } - out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, (size_t)9999)), thread_size) + ' ' + end : "" ) + + // Shorten process thread representation when larger than 5 digits: 10000 -> 10K ... + const std::string proc_threads_string = [&] { + if (p.threads > 9999) { + return std::to_string(p.threads / 1000) + 'K'; + } else { + return std::to_string(p.threads); + } + }(); + + out += (thread_size > 0 ? t_color + rjust(proc_threads_string, thread_size) + ' ' + end : "" ) + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") @@ -1545,8 +1911,11 @@ namespace Proc { if (numpids > select_max) { const int scroll_pos = clamp((int)round((double)start * select_max / (numpids - select_max)), 0, height - 5); out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up - + Mv::to(y + height - 2, x + width - 2) + Symbols::down - + Mv::to(y + 2 + scroll_pos, x + width - 2) + "█"; + + Mv::to(y + height - 2, x + width - 2) + Symbols::down; + + for (int i = y + 2; i < y + height - 2; i++) { + out += Mv::to(i, x + width - 2) + ((i == y + 2 + scroll_pos) ? "█" : " "); + } } //? Current selection and number of processes @@ -1558,25 +1927,18 @@ namespace Proc { //? Clear out left over graphs from dead processes at a regular interval if (not data_same and ++counter >= 100) { counter = 0; - for (auto element = p_graphs.begin(); element != p_graphs.end();) { - if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { - element = p_graphs.erase(element); - p_counters.erase(element->first); - } - else - ++element; - } - p_graphs.compact(); - p_counters.compact(); - for (auto element = p_wide_cmd.begin(); element != p_wide_cmd.end();) { - if (rng::find(plist, element->first, &proc_info::pid) == plist.end()) { - element = p_wide_cmd.erase(element); - } - else - ++element; - } - p_wide_cmd.compact(); + std::erase_if(p_graphs, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + std::erase_if(p_counters, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); + + std::erase_if(p_wide_cmd, [&](const auto& pair) { + return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); + }); } if (selected == 0 and selected_pid != 0) { @@ -1599,6 +1961,7 @@ namespace Draw { auto proc_left = Config::getB("proc_left"); Cpu::box.clear(); + Mem::box.clear(); Net::box.clear(); Proc::box.clear(); @@ -1619,6 +1982,24 @@ namespace Draw { Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true; Cpu::shown = s_contains(boxes, "cpu"); + #ifdef GPU_SUPPORT + Gpu::box.clear(); + Gpu::width = 0; + Gpu::shown_panels.clear(); + if (not Gpu::gpu_names.empty()) { + std::istringstream iss(boxes, std::istringstream::in); + string current; + while (iss >> current) + if ( current == "gpu0" + or current == "gpu1" + or current == "gpu2" + or current == "gpu3" + or current == "gpu4" + or current == "gpu5" + ) Gpu::shown_panels.push_back(current.back()-'0'); + } + Gpu::shown = Gpu::shown_panels.size(); + #endif Mem::shown = s_contains(boxes, "mem"); Net::shown = s_contains(boxes, "net"); Proc::shown = s_contains(boxes, "proc"); @@ -1626,13 +2007,35 @@ namespace Draw { //* Calculate and draw cpu box outlines if (Cpu::shown) { using namespace Cpu; - bool show_temp = (Config::getB("check_temp") and got_sensors); + #ifdef GPU_SUPPORT + const bool show_gpu_on = Config::getS("show_gpu_info") == "On"; + const bool gpus_shown_in_cpu_panel = Gpu::gpu_names.size() > 0 and ( + show_gpu_on or (Config::getS("cpu_graph_lower") == "Auto" and Gpu::shown == 0) + ); + const int gpus_height_offset = (Gpu::gpu_names.size() - Gpu::shown)*gpus_shown_in_cpu_panel; + int gpus_extra_height = gpus_shown_in_cpu_panel ? Gpu::gpu_names.size() - (show_gpu_on ? 0 : Gpu::shown) : 0; + #endif + const bool show_temp = (Config::getB("check_temp") and got_sensors); width = round((double)Term::width * width_p / 100); + #ifdef GPU_SUPPORT + if (Gpu::shown != 0 and not (Mem::shown or Net::shown or Proc::shown)) { + height = Term::height - Gpu::min_height*Gpu::shown - gpus_height_offset; + } else { + height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p/(Gpu::shown+1) + (Gpu::shown != 0)*5) / 100)); + } + if (height <= Term::height-gpus_height_offset) height += gpus_height_offset; + if (height - gpus_extra_height < 7) gpus_extra_height = height - 7; + #else height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100)); + #endif x = 1; y = cpu_bottom ? Term::height - height + 1 : 1; + #ifdef GPU_SUPPORT + b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - gpus_extra_height - 5))); + #else b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5))); + #endif if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) { b_column_size = 2; b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1); @@ -1650,7 +2053,11 @@ namespace Draw { } if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1; + #ifdef GPU_SUPPORT + b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4 + gpus_extra_height); + #else b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4); + #endif b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; @@ -1658,10 +2065,61 @@ namespace Draw { box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1); auto& custom = Config::getS("custom_cpu_name"); - const string cpu_title = uresize((custom.empty() ? Cpu::cpuName : custom) , b_width - 14); + static const bool hasCpuHz = not Cpu::get_cpuHz().empty(); + const string cpu_title = uresize( + (custom.empty() ? Cpu::cpuName : custom), + b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? 14 : 4) + ); box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title); } + #ifdef GPU_SUPPORT + //* Calculate and draw gpu box outlines + if (Gpu::shown != 0) { + using namespace Gpu; + x_vec.resize(shown); y_vec.resize(shown); + b_x_vec.resize(shown); b_y_vec.resize(shown); + b_height_vec.resize(shown); + box.resize(shown); + graph_upper_vec.resize(shown); graph_lower_vec.resize(shown); + temp_graph_vec.resize(shown); + mem_used_graph_vec.resize(shown); mem_util_graph_vec.resize(shown); + gpu_meter_vec.resize(shown); + pwr_meter_vec.resize(shown); + redraw.resize(shown); + for (auto i = 0; i < shown; ++i) { + redraw[i] = true; + + width = Term::width; + if (Cpu::shown) + if (not (Mem::shown or Net::shown or Proc::shown)) + height = min_height; + else height = Cpu::height; + else + if (not (Mem::shown or Net::shown or Proc::shown)) + height = Term::height/Gpu::shown + (i == 0)*(Term::height%Gpu::shown); + else + height = max(min_height, (int)ceil((double)Term::height * height_p/Gpu::shown / 100)); + + height += (height+Cpu::height == Term::height-1); + x_vec[i] = 1; y_vec[i] = 1 + i*height + (not Config::getB("cpu_bottom"))*Cpu::shown*Cpu::height; + box[i] = createBox(x_vec[i], y_vec[i], width, height, Theme::c("cpu_box"), true, std::string("gpu") + (char)(shown_panels[i]+'0'), "", (shown_panels[i]+5)%10); // TODO gpu_box + + b_height_vec[i] = 2 + gpu_b_height_offsets[shown_panels[i]]; + b_width = clamp(width/2, min_width, 64); + + //? Main statistics box + b_x_vec[i] = x_vec[i] + width - b_width - 1; + b_y_vec[i] = y_vec[i] + ceil((double)(height - 2) / 2) - ceil((double)(b_height_vec[i]) / 2) + 1; + + string name = Config::getS(std::string("custom_gpu_name") + (char)(shown_panels[i]+'0')); + if (name.empty()) name = gpu_names[shown_panels[i]]; + + box[i] += createBox(b_x_vec[i], b_y_vec[i], b_width, b_height_vec[i], "", false, name.substr(0, b_width-5)); + } + } + #endif + //* Calculate and draw mem box outlines if (Mem::shown) { using namespace Mem; @@ -1670,13 +2128,22 @@ namespace Draw { auto mem_graphs = Config::getB("mem_graphs"); width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); + #ifdef GPU_SUPPORT + height = ceil((double)Term::height * (100 - Net::height_p * Net::shown*4 / ((Gpu::shown != 0 and Cpu::shown) + 4)) / 100) - Cpu::height - Gpu::height*Gpu::shown; + #else height = ceil((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1; - if (height + Cpu::height > Term::height) height = Term::height - Cpu::height; + #endif x = (proc_left and Proc::shown) ? Term::width - width + 1: 1; if (mem_below_net and Net::shown) + #ifdef GPU_SUPPORT + y = Term::height - height + 1 - (cpu_bottom ? Cpu::height + Gpu::height*Gpu::shown : 0); + else + y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); else y = cpu_bottom ? 1 : Cpu::height + 1; + #endif if (show_disks) { mem_width = ceil((double)(width - 3) / 2); @@ -1725,10 +2192,18 @@ namespace Draw { if (Net::shown) { using namespace Net; width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); + #ifdef GPU_SUPPORT + height = Term::height - Cpu::height - Gpu::height*Gpu::shown - Mem::height; + #else height = Term::height - Cpu::height - Mem::height; + #endif x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1; if (mem_below_net and Mem::shown) + #ifdef GPU_SUPPORT + y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = cpu_bottom ? 1 : Cpu::height + 1; + #endif else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); @@ -1747,9 +2222,17 @@ namespace Draw { if (Proc::shown) { using namespace Proc; width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0)); + #ifdef GPU_SUPPORT + height = Term::height - Cpu::height - Gpu::height*Gpu::shown; + #else height = Term::height - Cpu::height; + #endif x = proc_left ? 1 : Term::width - width + 1; + #ifdef GPU_SUPPORT + y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; + #else y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1; + #endif select_max = height - 3; box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4); } diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index af57ed8..b049a57 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::deque; using std::string; @@ -108,7 +107,7 @@ namespace Draw { long long offset; long long last = 0, max_value = 0; bool current = true, tty_mode = false; - unordered_flat_map> graphs = { {true, {}}, {false, {}}}; + std::unordered_map> graphs = { {true, {}}, {false, {}}}; //* Create two representations of the graph to switch between to represent two values for each braille character void _create(const deque& data, int data_offset); @@ -135,6 +134,6 @@ namespace Draw { namespace Proc { extern Draw::TextEdit filter; - extern unordered_flat_map p_graphs; - extern unordered_flat_map p_counters; + extern std::unordered_map p_graphs; + extern std::unordered_map p_counters; } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index ebdee3b..fb9e46d 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -22,6 +22,15 @@ tab-size = 4 #include #include #include +#include + +#include "btop_input.hpp" +#include "btop_tools.hpp" +#include "btop_config.hpp" +#include "btop_shared.hpp" +#include "btop_menu.hpp" +#include "btop_draw.hpp" + #include "btop_input.hpp" #include "btop_tools.hpp" @@ -40,7 +49,7 @@ namespace rng = std::ranges; namespace Input { //* Map for translating key codes to readable values - const unordered_flat_map Key_escapes = { + const std::unordered_map Key_escapes = { {"\033", "escape"}, {"\n", "enter"}, {" ", "space"}, @@ -55,9 +64,13 @@ namespace Input { {"[C", "right"}, {"OC", "right"}, {"[2~", "insert"}, + {"[4h", "insert"}, {"[3~", "delete"}, + {"[P", "delete"}, {"[H", "home"}, + {"[1~", "home"}, {"[F", "end"}, + {"[4~", "end"}, {"[5~", "page_up"}, {"[6~", "page_down"}, {"\t", "tab"}, @@ -79,7 +92,7 @@ namespace Input { std::atomic interrupt (false); std::atomic polling (false); array mouse_pos; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; deque history(50, ""); string old_filter; @@ -260,11 +273,21 @@ namespace Input { Menu::show(Menu::Menus::Options); return; } - else if (is_in(key, "1", "2", "3", "4")) { + else if (key.size() == 1 and isint(key)) { + auto intKey = stoi(key); + #ifdef GPU_SUPPORT + static const array boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"}; + if ((intKey == 0 and Gpu::gpu_names.size() < 5) or (intKey >= 5 and std::cmp_less(Gpu::gpu_names.size(), intKey - 4))) + return; + #else + static const array boxes = {"", "cpu", "mem", "net", "proc"}; + if (intKey == 0 or intKey > 4) + return; + #endif atomic_wait(Runner::active); Config::current_preset = -1; - static const array boxes = {"cpu", "mem", "net", "proc"}; - Config::toggle_box(boxes.at(std::stoi(key) - 1)); + + Config::toggle_box(boxes.at(intKey)); Draw::calcSizes(); Runner::run("all", false, true); return; @@ -343,6 +366,9 @@ namespace Input { else if (key == "c") Config::flip("proc_per_core"); + else if (key == "%") + Config::flip("proc_mem_bytes"); + else if (key == "delete" and not Config::getS("proc_filter").empty()) Config::set("proc_filter", ""s); diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 5a1a28e..fc6fda0 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -21,10 +21,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -44,7 +43,7 @@ namespace Input { }; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; extern atomic interrupt; extern atomic polling; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index c9dfbdc..bc97dc2 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -17,7 +17,7 @@ tab-size = 4 */ #include -#include +#include #include #include #include @@ -31,7 +31,6 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_shared.hpp" -using robin_hood::unordered_flat_map; using std::array; using std::ceil; using std::max; @@ -54,18 +53,76 @@ namespace Menu { int signalKillRet{}; // defaults to 0 const array P_Signals = { - "0", + "0", +#ifdef __linux__ +#if defined(__hppa__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGSTKFLT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGXCPU", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1", + "SIGUSR2", "SIGCHLD", "SIGPWR", "SIGVTALRM", + "SIGPROF", "SIGIO", "SIGWINCH", "SIGSTOP", + "SIGTSTP", "SIGCONT", "SIGTTIN", "SIGTTOU", + "SIGURG", "SIGXFSZ", "SIGSYS" +#elif defined(__mips__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1", + "SIGUSR2", "SIGCHLD", "SIGPWR", "SIGWINCH", + "SIGURG", "SIGIO", "SIGSTOP", "SIGTSTP", + "SIGCONT", "SIGTTIN", "SIGTTOU", "SIGVTALRM", + "SIGPROF", "SIGXCPU", "SIGXFSZ" +#elif defined(__alpha__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGPWR", "SIGUSR1", "SIGUSR2" +#elif defined (__sparc__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGLOST", "SIGUSR1", "SIGUSR2" +#else "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", - "SIGPIPE", "SIGALRM", "SIGTERM", "16", "SIGCHLD", - "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", - "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", - "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", - "SIGPWR", "SIGSYS" + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", + "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", + "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGIO", "SIGPWR", "SIGSYS" +#endif +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__) + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", + "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", + "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", + "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", + "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGINFO", "SIGUSR1", "SIGUSR2" +#else + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "7", "SIGFPE", + "SIGKILL", "10", "SIGSEGV", "12", + "SIGPIPE", "SIGALRM", "SIGTERM", "16", + "17", "18", "19", "20", + "21", "22", "23", "24", + "25", "26", "27", "28", + "29", "30", "31" +#endif }; - unordered_flat_map mouse_mappings; + std::unordered_map mouse_mappings; const array, 3> menu_normal = { array{ @@ -115,6 +172,7 @@ namespace Menu { {"2", "Toggle MEM box."}, {"3", "Toggle NET box."}, {"4", "Toggle PROC box."}, + {"5", "Toggle GPU box."}, {"d", "Toggle disks view in MEM box."}, {"F2, o", "Shows options."}, {"F1, ?, h", "Shows this window."}, @@ -137,6 +195,7 @@ namespace Menu { {"c", "Toggle per-core cpu usage of processes."}, {"r", "Reverse sorting order in processes box."}, {"e", "Toggle processes tree view."}, + {"%", "Toggles memory display mode in processes box."}, {"Selected +, -", "Expand/collapse the selected process in tree view."}, {"Selected t", "Terminate selected process with SIGTERM - 15."}, {"Selected k", "Kill selected process with SIGKILL - 9."}, @@ -212,6 +271,9 @@ namespace Menu { "Manually set which boxes to show.", "", "Available values are \"cpu mem net proc\".", + #ifdef GPU_SUPPORT + "Or \"gpu0\" through \"gpu5\" for GPU boxes.", + #endif "Separate values with whitespace.", "", "Toggle between presets with key \"p\"."}, @@ -314,23 +376,49 @@ namespace Menu { {"cpu_graph_upper", "Cpu upper graph.", "", - "Sets the CPU stat shown in upper half of", + "Sets the CPU/GPU stat shown in upper half of", "the CPU graph.", "", - "\"total\" = Total cpu usage.", + "CPU:", + "\"total\" = Total cpu usage. (Auto)", "\"user\" = User mode cpu usage.", "\"system\" = Kernel mode cpu usage.", - "+ more depending on kernel."}, + "+ more depending on kernel.", + #ifdef GPU_SUPPORT + "", + "GPU:", + "\"gpu-totals\" = GPU usage split by device.", + "\"gpu-vram-totals\" = VRAM usage split by GPU.", + "\"gpu-pwr-totals\" = Power usage split by GPU.", + "\"gpu-average\" = Avg usage of all GPUs.", + "\"gpu-vram-total\" = VRAM usage of all GPUs.", + "\"gpu-pwr-total\" = Power usage of all GPUs.", + "Not all stats are supported on all devices." + #endif + }, {"cpu_graph_lower", "Cpu lower graph.", "", - "Sets the CPU stat shown in lower half of", + "Sets the CPU/GPU stat shown in lower half of", "the CPU graph.", "", + "CPU:", "\"total\" = Total cpu usage.", "\"user\" = User mode cpu usage.", "\"system\" = Kernel mode cpu usage.", - "+ more depending on kernel."}, + "+ more depending on kernel.", + #ifdef GPU_SUPPORT + "", + "GPU:", + "\"gpu-totals\" = GPU usage split/device. (Auto)", + "\"gpu-vram-totals\" = VRAM usage split by GPU.", + "\"gpu-pwr-totals\" = Power usage split by GPU.", + "\"gpu-average\" = Avg usage of all GPUs.", + "\"gpu-vram-total\" = VRAM usage of all GPUs.", + "\"gpu-pwr-total\" = Power usage of all GPUs.", + "Not all stats are supported on all devices." + #endif + }, {"cpu_invert_lower", "Toggles orientation of the lower CPU graph.", "", @@ -342,12 +430,24 @@ namespace Menu { "to fit to box height.", "", "True or False."}, + #ifdef GPU_SUPPORT + {"show_gpu_info", + "Show gpu info in cpu box.", + "", + "Toggles gpu stats in cpu box and the", + "gpu graph (if \"cpu_graph_lower\" is set to", + "\"Auto\").", + "", + "\"Auto\" to show when no gpu box is shown.", + "\"On\" to always show.", + "\"Off\" to never show."}, + #endif {"check_temp", "Enable cpu temperature reporting.", "", "True or False."}, {"cpu_sensor", - "Cpu temperature sensor", + "Cpu temperature sensor.", "", "Select the sensor that corresponds to", "your cpu temperature.", @@ -387,7 +487,7 @@ namespace Menu { "Rankine, 0 = abosulte zero, 1 degree change", "equals 1 degree change in Fahrenheit."}, {"show_cpu_freq", - "Show CPU frequency", + "Show CPU frequency.", "", "Can cause slowdowns on systems with many", "cores and certain kernel versions."}, @@ -403,6 +503,50 @@ namespace Menu { "", "True or False."}, }, + #ifdef GPU_SUPPORT + { + {"nvml_measure_pcie_speeds", + "Measure PCIe throughput on NVIDIA cards.", + "", + "May impact performance on certain cards.", + "", + "True or False."}, + {"graph_symbol_gpu", + "Graph symbol to use for graphs in gpu box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"gpu_mirror_graph", + "Horizontally mirror the GPU graph.", + "", + "True or False."}, + {"custom_gpu_name0", + "Custom gpu0 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name1", + "Custom gpu1 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name2", + "Custom gpu2 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name3", + "Custom gpu3 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name4", + "Custom gpu4 model name in gpu stats box.", + "", + "Empty string to disable."}, + {"custom_gpu_name5", + "Custom gpu5 model name in gpu stats box.", + "", + "Empty string to disable."}, + }, + #endif { {"mem_below_net", "Mem box location.", @@ -589,6 +733,11 @@ namespace Menu { "Set true to show processes grouped by", "parents with lines drawn between parent", "and child process."}, + {"proc_aggregate", + "Aggregate child's resources in parent.", + "", + "In tree-view, include all child resources", + "with the parent even while expanded."}, {"proc_colors", "Enable colors in process view.", "", @@ -1015,7 +1164,7 @@ namespace Menu { static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; - static const unordered_flat_map>> optionsList = { + static const std::unordered_map>> optionsList = { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, @@ -1029,6 +1178,10 @@ namespace Menu { {"cpu_graph_lower", std::cref(Cpu::available_fields)}, {"cpu_sensor", std::cref(Cpu::available_sensors)}, {"selected_battery", std::cref(Config::available_batteries)}, + #ifdef GPU_SUPPORT + {"show_gpu_info", std::cref(Config::show_gpu_values)}, + {"graph_symbol_gpu", std::cref(Config::valid_graph_symbols_def)}, + #endif }; auto tty_mode = Config::getB("tty_mode"); auto vim_keys = Config::getB("vim_keys"); @@ -1080,7 +1233,8 @@ namespace Menu { const auto& option = categories[selected_cat][item_height * page + selected][0]; if (selPred.test(isString) and Config::stringValid(option, editor.text)) { Config::set(option, editor.text); - if (option == "custom_cpu_name") screen_redraw = true; + if (option == "custom_cpu_name" or option.starts_with("custom_gpu_name")) + screen_redraw = true; else if (is_in(option, "shown_boxes", "presets")) { screen_redraw = true; Config::current_preset = -1; @@ -1161,7 +1315,7 @@ namespace Menu { if (--selected_cat < 0) selected_cat = (int)categories.size() - 1; page = selected = 0; } - else if (is_in(key, "1", "2", "3", "4", "5") or key.starts_with("select_cat_")) { + else if (is_in(key, "1", "2", "3", "4", "5", "6") or key.starts_with("select_cat_")) { selected_cat = key.back() - '0' - 1; page = selected = 0; } @@ -1213,7 +1367,7 @@ namespace Menu { Logger::set(optList.at(i)); Logger::info("Logger set to " + optList.at(i)); } - else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) + else if (is_in(option, "proc_sorting", "cpu_sensor", "show_gpu_info") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) screen_redraw = true; } else @@ -1259,11 +1413,19 @@ namespace Menu { //? Category buttons out += Mv::to(y+7, x+4); + #ifdef GPU_SUPPORT + for (int i = 0; const auto& m : {"general", "cpu", "gpu", "mem", "net", "proc"}) { + #else for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) { + #endif out += Fx::b + (i == selected_cat ? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']' : Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ') + #ifdef GPU_SUPPORT + + Mv::r(7); + #else + Mv::r(10); + #endif if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name)) mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15}; i++; diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 0e8832f..74b30d1 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -38,7 +38,7 @@ namespace Menu { extern bool redraw; //? line, col, height, width - extern unordered_flat_map mouse_mappings; + extern std::unordered_map mouse_mappings; //* Creates a message box centered on screen //? Height of box is determined by size of content vector diff --git a/src/btop_shared.cpp b/src/btop_shared.cpp index e5c9027..b159a97 100644 --- a/src/btop_shared.cpp +++ b/src/btop_shared.cpp @@ -18,12 +18,25 @@ tab-size = 4 #include +#include "btop_config.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" namespace rng = std::ranges; using namespace Tools; +#ifdef GPU_SUPPORT +namespace Gpu { + vector gpu_names; + vector gpu_b_height_offsets; + std::unordered_map> shared_gpu_percent = { + {"gpu-average", {}}, + {"gpu-vram-total", {}}, + {"gpu-pwr-total", {}}, + }; + long long gpu_pwr_total_max; +} +#endif namespace Proc { void proc_sorter(vector& proc_vec, const string& sorting, bool reverse, bool tree) { @@ -156,6 +169,12 @@ namespace Proc { filter_found++; p.filtered = true; } + else if (Config::getB("proc_aggregate")) { + cur_proc.cpu_p += p.cpu_p; + cur_proc.cpu_c += p.cpu_c; + cur_proc.mem += p.mem; + cur_proc.threads += p.threads; + } } if (collapsed or filtering) { return; diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index ecf97f2..ebd1073 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,10 +26,9 @@ tab-size = 4 #include #include #include -#include +#include #include -using robin_hood::unordered_flat_map; using std::array; using std::atomic; using std::deque; @@ -86,6 +85,91 @@ namespace Shared { } +namespace Gpu { +#ifdef GPU_SUPPORT + extern vector box; + extern int width, height, min_width, min_height; + extern vector x_vec, y_vec; + extern vector redraw; + extern int shown; + extern vector shown_panels; + extern vector gpu_names; + extern vector gpu_b_height_offsets; + extern long long gpu_pwr_total_max; + + extern std::unordered_map> shared_gpu_percent; // averages, power/vram total + + const array mem_names { "used"s, "free"s }; + + //* Container for process information // TODO + /*struct proc_info { + unsigned int pid; + unsigned long long mem; + };*/ + + //* Container for supported Gpu::*::collect() functions + struct gpu_info_supported { + bool gpu_utilization = true, + mem_utilization = true, + gpu_clock = true, + mem_clock = true, + pwr_usage = true, + pwr_state = true, + temp_info = true, + mem_total = true, + mem_used = true, + pcie_txrx = true; + }; + + //* Per-device container for GPU info + struct gpu_info { + std::unordered_map> gpu_percent = { + {"gpu-totals", {}}, + {"gpu-vram-totals", {}}, + {"gpu-pwr-totals", {}}, + }; + unsigned int gpu_clock_speed; // MHz + + long long pwr_usage; // mW + long long pwr_max_usage = 255000; + long long pwr_state; + + deque temp = {0}; + long long temp_max = 110; + + long long mem_total = 0; + long long mem_used = 0; + deque mem_utilization_percent = {0}; // TODO: properly handle GPUs that can't report some stats + long long mem_clock_speed = 0; // MHz + + long long pcie_tx = 0; // KB/s + long long pcie_rx = 0; + + gpu_info_supported supported_functions; + + // vector graphics_processes = {}; // TODO + // vector compute_processes = {}; + }; + + namespace Nvml { + extern bool shutdown(); + } + namespace Rsmi { + extern bool shutdown(); + } + + //* Collect gpu stats and temperatures + auto collect(bool no_update = false) -> vector&; + + //* Draw contents of gpu box using as source + string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same); +#else + struct gpu_info { + bool supported = false; + }; +#endif +} + namespace Cpu { extern string box; extern int x, y, width, height, min_width, min_height; @@ -96,7 +180,7 @@ namespace Cpu { extern tuple current_bat; struct cpu_info { - unordered_flat_map> cpu_percent = { + std::unordered_map> cpu_percent = { {"total", {}}, {"user", {}}, {"nice", {}}, @@ -119,11 +203,13 @@ namespace Cpu { auto collect(bool no_update = false) -> cpu_info&; //* Draw contents of cpu box using as source - string draw(const cpu_info& cpu, bool force_redraw = false, bool data_same = false); + string draw(const cpu_info& cpu, const vector& gpu, bool force_redraw = false, bool data_same = false); //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> unordered_flat_map; - extern unordered_flat_map core_mapping; + auto get_core_mapping() -> std::unordered_map; + extern std::unordered_map core_mapping; + + auto get_cpuHz() -> string; //* Get battery info from /sys auto get_battery() -> tuple; @@ -155,13 +241,13 @@ namespace Mem { }; struct mem_info { - unordered_flat_map stats = + std::unordered_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - unordered_flat_map> percent = + std::unordered_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; - unordered_flat_map disks; + std::unordered_map disks; vector disks_order; }; @@ -183,7 +269,7 @@ namespace Net { extern string selected_iface; extern vector interfaces; extern bool rescale; - extern unordered_flat_map graph_max; + extern std::unordered_map graph_max; struct net_stat { uint64_t speed{}; // defaults to 0 @@ -195,14 +281,14 @@ namespace Net { }; struct net_info { - unordered_flat_map> bandwidth = { {"download", {}}, {"upload", {}} }; - unordered_flat_map stat = { {"download", {}}, {"upload", {}} }; + std::unordered_map> bandwidth = { {"download", {}}, {"upload", {}} }; + std::unordered_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" bool connected{}; // defaults to false }; - extern unordered_flat_map current_net; + extern std::unordered_map current_net; //* Collect net upload/download stats auto collect(bool no_update=false) -> net_info&; @@ -235,7 +321,7 @@ namespace Proc { }; //? Translation from process state char to explanative string - const unordered_flat_map proc_states = { + const std::unordered_map proc_states = { {'R', "Running"}, {'S', "Sleeping"}, {'D', "Waiting"}, diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 8a641fe..136adbf 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -42,11 +42,11 @@ namespace Theme { fs::path theme_dir; fs::path user_theme_dir; vector themes; - unordered_flat_map colors; - unordered_flat_map> rgbs; - unordered_flat_map> gradients; + std::unordered_map colors; + std::unordered_map> rgbs; + std::unordered_map> gradients; - const unordered_flat_map Default_theme = { + const std::unordered_map Default_theme = { { "main_bg", "#00" }, { "main_fg", "#cc" }, { "title", "#ee" }, @@ -91,7 +91,7 @@ namespace Theme { { "process_end", "#d45454" } }; - const unordered_flat_map TTY_theme = { + const std::unordered_map TTY_theme = { { "main_bg", "\x1b[0;40m" }, { "main_fg", "\x1b[37m" }, { "title", "\x1b[97m" }, @@ -224,7 +224,7 @@ namespace Theme { } //* Generate colors and rgb decimal vectors for the theme - void generateColors(const unordered_flat_map& source) { + void generateColors(const std::unordered_map& source) { vector t_rgb; string depth; bool t_to_256 = Config::getB("lowcolor"); @@ -372,7 +372,7 @@ namespace Theme { //* Load a .theme file from disk auto loadFile(const string& filename) { - unordered_flat_map theme_out; + std::unordered_map theme_out; const fs::path filepath = filename; if (not fs::exists(filepath)) return Default_theme; diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index 3fb30a4..cc3fbdf 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -22,12 +22,11 @@ tab-size = 4 #include #include #include -#include +#include using std::array; using std::string; using std::vector; -using robin_hood::unordered_flat_map; namespace Theme { extern std::filesystem::path theme_dir; @@ -54,9 +53,9 @@ namespace Theme { //* Set current theme from current "color_theme" value in config void setTheme(); - extern unordered_flat_map colors; - extern unordered_flat_map> rgbs; - extern unordered_flat_map> gradients; + extern std::unordered_map colors; + extern std::unordered_map> rgbs; + extern std::unordered_map> gradients; //* Return escape code for color inline const string& c(const string& name) { return colors.at(name); } diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index a8add08..e17edf8 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -30,7 +30,7 @@ tab-size = 4 #include #include -#include "robin_hood.h" +#include "unordered_map" #include "widechar_width.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" @@ -43,7 +43,6 @@ using std::flush; using std::max; using std::string_view; using std::to_string; -using robin_hood::unordered_flat_map; using namespace std::literals; // to use operator""s @@ -99,19 +98,31 @@ namespace Term { } auto get_min_size(const string& boxes) -> array { - bool cpu = boxes.find("cpu") != string::npos; - bool mem = boxes.find("mem") != string::npos; - bool net = boxes.find("net") != string::npos; - bool proc = boxes.find("proc") != string::npos; - int width = 0; + bool cpu = boxes.find("cpu") != string::npos; + bool mem = boxes.find("mem") != string::npos; + bool net = boxes.find("net") != string::npos; + bool proc = boxes.find("proc") != string::npos; + #ifdef GPU_SUPPORT + int gpu = 0; + if (not Gpu::gpu_names.empty()) + for (char i = '0'; i <= '5'; ++i) + gpu += (boxes.find(std::string("gpu") + i) != string::npos); + #endif + int width = 0; if (mem) width = Mem::min_width; else if (net) width = Mem::min_width; width += (proc ? Proc::min_width : 0); if (cpu and width < Cpu::min_width) width = Cpu::min_width; + #ifdef GPU_SUPPORT + if (gpu != 0 and width < Gpu::min_width) width = Gpu::min_width; + #endif int height = (cpu ? Cpu::min_height : 0); if (proc) height += Proc::min_height; else height += (mem ? Mem::min_height : 0) + (net ? Net::min_height : 0); + #ifdef GPU_SUPPORT + height += Gpu::min_height*gpu; + #endif return { width, height }; } @@ -445,7 +456,7 @@ namespace Tools { out = to_string((int)round(stod(out))); } if (out.size() > 3) { - out = to_string((int)(out[0] - '0') + 1); + out = to_string((int)(out[0] - '0')) + ".0"; start++; } out.push_back(units[start][0]); @@ -537,6 +548,74 @@ namespace Tools { return (user != nullptr ? user : ""); } + DebugTimer::DebugTimer(const string name, bool start, bool delayed_report) : name(name), delayed_report(delayed_report) { + if (start) + this->start(); + } + + DebugTimer::~DebugTimer() { + if (running) + this->stop(true); + this->force_report(); + } + + void DebugTimer::start() { + if (running) return; + running = true; + start_time = time_micros(); + } + + void DebugTimer::stop(bool report) { + if (not running) return; + running = false; + elapsed_time = time_micros() - start_time; + if (report) this->report(); + } + + void DebugTimer::reset(bool restart) { + running = false; + start_time = 0; + elapsed_time = 0; + if (restart) this->start(); + } + + void DebugTimer::stop_rename_reset(const string &new_name, bool report, bool restart) { + this->stop(report); + name = new_name; + this->reset(restart); + } + + void DebugTimer::report() { + string report_line; + if (start_time == 0 and elapsed_time == 0) + report_line = fmt::format("DebugTimer::report() warning -> Timer [{}] has not been started!", name); + else if (running) + report_line = fmt::format(custom_locale, "Timer [{}] (running) currently at {:L} μs", name, time_micros() - start_time); + else + report_line = fmt::format(custom_locale, "Timer [{}] took {:L} μs", name, elapsed_time); + + if (delayed_report) + report_buffer.emplace_back(report_line); + else + Logger::log_write(log_level, report_line); + } + + void DebugTimer::force_report() { + if (report_buffer.empty()) return; + for (const auto& line : report_buffer) + Logger::log_write(log_level, line); + report_buffer.clear(); + } + + uint64_t DebugTimer::elapsed() { + if (running) + return time_micros() - start_time; + return elapsed_time; + } + + bool DebugTimer::is_running() { + return running; + } } namespace Logger { @@ -568,7 +647,7 @@ namespace Logger { loglevel = v_index(log_levels, level); } - void log_write(const size_t level, const string& msg) { + void log_write(const Level level, const string& msg) { if (loglevel < level or logfile.empty()) return; atomic_lock lck(busy, true); lose_priv neutered{}; diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index f4bc36d..bda89f2 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -31,6 +31,10 @@ tab-size = 4 #include #include #include +#include +#ifdef BTOP_DEBUG +#include +#endif #ifndef HOST_NAME_MAX #ifdef __APPLE__ #define HOST_NAME_MAX 255 @@ -146,11 +150,46 @@ namespace Term { void restore(); } +//* Simple logging implementation +namespace Logger { + const vector log_levels = { + "DISABLED", + "ERROR", + "WARNING", + "INFO", + "DEBUG", + }; + extern std::filesystem::path logfile; + + enum Level : size_t { + DISABLED = 0, + ERROR = 1, + WARNING = 2, + INFO = 3, + DEBUG = 4, + }; + + //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" + void set(const string& level); + + void log_write(const Level level, const string& msg); + inline void error(const string msg) { log_write(ERROR, msg); } + inline void warning(const string msg) { log_write(WARNING, msg); } + inline void info(const string msg) { log_write(INFO, msg); } + inline void debug(const string msg) { log_write(DEBUG, msg); } +} + //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Tools { constexpr auto SSmax = std::numeric_limits::max(); + class MyNumPunct : public std::numpunct { + protected: + virtual char do_thousands_sep() const { return '\''; } + virtual std::string do_grouping() const { return "\03"; } + }; + size_t wide_ulen(const string& str); size_t wide_ulen(const std::wstring& w_str); @@ -298,6 +337,50 @@ namespace Tools { //* Add std::string operator * : Repeat string number of times std::string operator*(const string& str, int64_t n); + template +#ifdef BTOP_DEBUG + const T& safeVal(const std::unordered_map& map, const K& key, const T& fallback = T{}, std::source_location loc = std::source_location::current()) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] in file: {} on line: {}", key, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + const T& safeVal(const std::unordered_map& map, const K& key, const T& fallback = T{}) { + if (map.contains(key)) { + return map.at(key); + } else { + Logger::error(fmt::format("safeVal() called with invalid key: [{}] (Compile btop with DEBUG=true for more extensive logging!)", key)); + return fallback; + } + }; +#endif + + template +#ifdef BTOP_DEBUG + const T& safeVal(const std::vector& vec, const size_t& index, const T& fallback = T{}, std::source_location loc = std::source_location::current()) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] in file: {} on line: {}", index, loc.file_name(), loc.line())); + return fallback; + } + }; +#else + const T& safeVal(const std::vector& vec, const size_t& index, const T& fallback = T{}) { + if (index < vec.size()) { + return vec.at(index); + } else { + Logger::error(fmt::format("safeVal() called with invalid index: [{}] (Compile btop with DEBUG=true for more extensive logging!)", index)); + return fallback; + } + }; +#endif + + + //* Return current time in format string strf_time(const string& strf); @@ -334,27 +417,40 @@ namespace Tools { //* Convert a celsius value to celsius, fahrenheit, kelvin or rankin and return tuple with new value and unit. auto celsius_to(const long long& celsius, const string& scale) -> tuple; - } -//* Simple logging implementation -namespace Logger { - const vector log_levels = { - "DISABLED", - "ERROR", - "WARNING", - "INFO", - "DEBUG", +namespace Tools { + //* Creates a named timer that is started on construct (by default) and reports elapsed time in microseconds to Logger::debug() on destruct if running + //* Unless delayed_report is set to false, all reporting is buffered and delayed until DebugTimer is destructed or .force_report() is called + //* Usage example: Tools::DebugTimer timer(name:"myTimer", [start:true], [delayed_report:true]) // Create timer and start + //* timer.stop(); // Stop timer and report elapsed time + //* timer.stop_rename_reset("myTimer2"); // Stop timer, report elapsed time, rename timer, reset and restart + class DebugTimer { + uint64_t start_time{}; + uint64_t elapsed_time{}; + bool running{}; + std::locale custom_locale = std::locale(std::locale::classic(), new Tools::MyNumPunct); + vector report_buffer{}; + public: + string name{}; + bool delayed_report{}; + Logger::Level log_level = Logger::DEBUG; + DebugTimer() = default; + DebugTimer(const string name, bool start = true, bool delayed_report = true); + ~DebugTimer(); + + void start(); + void stop(bool report = true); + void reset(bool restart = true); + //* Stops and reports (default), renames timer then resets and restarts (default) + void stop_rename_reset(const string& new_name, bool report = true, bool restart = true); + void report(); + void force_report(); + uint64_t elapsed(); + bool is_running(); }; - extern std::filesystem::path logfile; - //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG" - void set(const string& level); - - void log_write(const size_t level, const string& msg); - inline void error(const string msg) { log_write(1, msg); } - inline void warning(const string msg) { log_write(2, msg); } - inline void info(const string msg) { log_write(3, msg); } - inline void debug(const string msg) { log_write(4, msg); } } + + diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 47598f0..2db49a2 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -58,6 +58,7 @@ tab-size = 4 #include #include #include +#include #include "../btop_config.hpp" #include "../btop_shared.hpp" @@ -74,7 +75,7 @@ using namespace Tools; namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields = {"total"}; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; bool got_sensors = false, cpu_temp_only = false; @@ -98,7 +99,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -169,17 +170,23 @@ namespace Shared { Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Logger::debug("Init -> Cpu::collect()"); Cpu::collect(); for (auto &[field, vec] : Cpu::current_cpu.cpu_percent) { if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); } + Logger::debug("Init -> Cpu::get_cpuName()"); Cpu::cpuName = Cpu::get_cpuName(); + Logger::debug("Init -> Cpu::get_sensors()"); Cpu::got_sensors = Cpu::get_sensors(); + Logger::debug("Init -> Cpu::get_core_mapping()"); Cpu::core_mapping = Cpu::get_core_mapping(); //? Init for namespace Mem Mem::old_uptime = system_uptime(); + Logger::debug("Init -> Mem::collect()"); Mem::collect(); + Logger::debug("Init -> Mem::get_zpools()"); Mem::get_zpools(); } @@ -204,7 +211,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -323,8 +330,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -557,7 +564,7 @@ namespace Mem { } } - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { // this bit is for 'regular' mounts static struct statinfo cur; long double etime = 0; @@ -572,7 +579,7 @@ namespace Mem { auto d = cur.dinfo->devices[i]; string devStatName = "/dev/" + string(d.device_name) + std::to_string(d.unit_number); for (auto& [ignored, disk] : disks) { // find matching mountpoints - could be multiple as d.device_name is only ada (and d.unit_number is the device number), while the disk.dev is like /dev/ada0s1 - if (disk.dev.string().rfind(devStatName, 0) == 0) { + if (disk.dev.string().rfind(devStatName, 0) == 0 and mapping.contains(disk.dev)) { devstat_compute_statistics(&d, nullptr, etime, DSM_TOTAL_BYTES_READ, &total_bytes_read, DSM_TOTAL_BYTES_WRITE, &total_bytes_write, DSM_NONE); assign_values(disk, total_bytes_read, total_bytes_write); string mountpoint = mapping.at(disk.dev); @@ -581,7 +588,6 @@ namespace Mem { } } - Logger::debug(""); } // this code is for ZFS mounts @@ -691,7 +697,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -807,13 +813,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -892,7 +898,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -966,7 +972,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1037,7 +1042,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 8a475e2..677c3fe 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -17,7 +17,8 @@ tab-size = 4 */ #include -#include +#include +#include #include #include #include @@ -29,6 +30,14 @@ tab-size = 4 #include #include // for inet_ntop() #include +#include +#include +#include +#include + +#if defined(RSMI_STATIC) + #include +#endif #if !(defined(STATIC_BUILD) && defined(__GLIBC__)) #include @@ -48,18 +57,23 @@ using std::numeric_limits; using std::round; using std::streamsize; using std::vector; +using std::future; +using std::async; +using std::pair; + namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; using namespace std::literals; // for operator""s +using namespace std::chrono_literals; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; @@ -83,10 +97,110 @@ namespace Cpu { int64_t crit{}; // defaults to 0 }; - unordered_flat_map found_sensors; + std::unordered_map found_sensors; string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; +} + +namespace Gpu { + vector gpus; +#ifdef GPU_SUPPORT + //? NVIDIA data collection + namespace Nvml { + //? NVML defines, structs & typedefs + #define NVML_DEVICE_NAME_BUFFER_SIZE 64 + #define NVML_SUCCESS 0 + #define NVML_TEMPERATURE_THRESHOLD_SHUTDOWN 0 + #define NVML_CLOCK_GRAPHICS 0 + #define NVML_CLOCK_MEM 2 + #define NVML_TEMPERATURE_GPU 0 + #define NVML_PCIE_UTIL_TX_BYTES 0 + #define NVML_PCIE_UTIL_RX_BYTES 1 + + typedef void* nvmlDevice_t; // we won't be accessing any of the underlying struct's properties, so this is fine + typedef int nvmlReturn_t, // enums are basically ints + nvmlTemperatureThresholds_t, + nvmlClockType_t, + nvmlPstates_t, + nvmlTemperatureSensors_t, + nvmlPcieUtilCounter_t; + + struct nvmlUtilization_t {unsigned int gpu, memory;}; + struct nvmlMemory_t {unsigned long long total, free, used;}; + + //? Function pointers + const char* (*nvmlErrorString)(nvmlReturn_t); + nvmlReturn_t (*nvmlInit)(); + nvmlReturn_t (*nvmlShutdown)(); + nvmlReturn_t (*nvmlDeviceGetCount)(unsigned int*); + nvmlReturn_t (*nvmlDeviceGetHandleByIndex)(unsigned int, nvmlDevice_t*); + nvmlReturn_t (*nvmlDeviceGetName)(nvmlDevice_t, char*, unsigned int); + nvmlReturn_t (*nvmlDeviceGetPowerManagementLimit)(nvmlDevice_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetTemperatureThreshold)(nvmlDevice_t, nvmlTemperatureThresholds_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetUtilizationRates)(nvmlDevice_t, nvmlUtilization_t*); + nvmlReturn_t (*nvmlDeviceGetClockInfo)(nvmlDevice_t, nvmlClockType_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetPowerUsage)(nvmlDevice_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetPowerState)(nvmlDevice_t, nvmlPstates_t*); + nvmlReturn_t (*nvmlDeviceGetTemperature)(nvmlDevice_t, nvmlTemperatureSensors_t, unsigned int*); + nvmlReturn_t (*nvmlDeviceGetMemoryInfo)(nvmlDevice_t, nvmlMemory_t*); + nvmlReturn_t (*nvmlDeviceGetPcieThroughput)(nvmlDevice_t, nvmlPcieUtilCounter_t, unsigned int*); + + //? Data + void* nvml_dl_handle; + bool initialized = false; + bool init(); + bool shutdown(); + template bool collect(gpu_info* gpus_slice); + vector devices; + unsigned int device_count = 0; + } + + //? AMD data collection + namespace Rsmi { + #if !defined(RSMI_STATIC) + //? RSMI defines, structs & typedefs + #define RSMI_MAX_NUM_FREQUENCIES 32 + #define RSMI_STATUS_SUCCESS 0 + #define RSMI_MEM_TYPE_VRAM 0 + #define RSMI_TEMP_CURRENT 0 + #define RSMI_TEMP_TYPE_EDGE 0 + #define RSMI_CLK_TYPE_MEM 4 + #define RSMI_CLK_TYPE_SYS 0 + #define RSMI_TEMP_MAX 1 + + typedef int rsmi_status_t, + rsmi_temperature_metric_t, + rsmi_clk_type_t, + rsmi_memory_type_t; + + struct rsmi_frequencies_t {uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; + + //? Function pointers + rsmi_status_t (*rsmi_init)(uint64_t); + rsmi_status_t (*rsmi_shut_down)(); + rsmi_status_t (*rsmi_num_monitor_devices)(uint32_t*); + rsmi_status_t (*rsmi_dev_name_get)(uint32_t, char*, size_t); + rsmi_status_t (*rsmi_dev_power_cap_get)(uint32_t, uint32_t, uint64_t*); + rsmi_status_t (*rsmi_dev_temp_metric_get)(uint32_t, uint32_t, rsmi_temperature_metric_t, int64_t*); + rsmi_status_t (*rsmi_dev_busy_percent_get)(uint32_t, uint32_t*); + rsmi_status_t (*rsmi_dev_memory_busy_percent_get)(uint32_t, uint32_t*); + rsmi_status_t (*rsmi_dev_gpu_clk_freq_get)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t*); + rsmi_status_t (*rsmi_dev_power_ave_get)(uint32_t, uint32_t, uint64_t*); + rsmi_status_t (*rsmi_dev_memory_total_get)(uint32_t, rsmi_memory_type_t, uint64_t*); + rsmi_status_t (*rsmi_dev_memory_usage_get)(uint32_t, rsmi_memory_type_t, uint64_t*); + rsmi_status_t (*rsmi_dev_pci_throughput_get)(uint32_t, uint64_t*, uint64_t*, uint64_t*); + + //? Data + void* rsmi_dl_handle; + #endif + bool initialized = false; + bool init(); + bool shutdown(); + template bool collect(gpu_info* gpus_slice); + uint32_t device_count = 0; + } +#endif } namespace Mem { @@ -139,7 +253,7 @@ namespace Shared { Cpu::collect(); if (Runner::coreNum_reset) Runner::coreNum_reset = false; for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) { - if (not vec.empty()) Cpu::available_fields.push_back(field); + if (not vec.empty() and not v_contains(Cpu::available_fields, field)) Cpu::available_fields.push_back(field); } Cpu::cpuName = Cpu::get_cpuName(); Cpu::got_sensors = Cpu::get_sensors(); @@ -148,12 +262,32 @@ namespace Shared { } Cpu::core_mapping = Cpu::get_core_mapping(); + //? Init for namespace Gpu + #ifdef GPU_SUPPORT + Gpu::Nvml::init(); + Gpu::Rsmi::init(); + if (not Gpu::gpu_names.empty()) { + for (auto const& [key, _] : Gpu::gpus[0].gpu_percent) + Cpu::available_fields.push_back(key); + for (auto const& [key, _] : Gpu::shared_gpu_percent) + Cpu::available_fields.push_back(key); + + using namespace Gpu; + gpu_b_height_offsets.resize(gpus.size()); + for (size_t i = 0; i < gpu_b_height_offsets.size(); ++i) + gpu_b_height_offsets[i] = gpus[i].supported_functions.gpu_utilization + + gpus[i].supported_functions.pwr_usage + + (gpus[i].supported_functions.mem_total or gpus[i].supported_functions.mem_used) + * (1 + 2*(gpus[i].supported_functions.mem_total and gpus[i].supported_functions.mem_used) + 2*gpus[i].supported_functions.mem_utilization); + } + #endif + //? Init for namespace Mem Mem::old_uptime = system_uptime(); Mem::collect(); + Logger::debug("Shared::init() : Initialized."); } - } namespace Cpu { @@ -167,7 +301,7 @@ namespace Cpu { "irq"s, "softirq"s, "steal"s, "guest"s, "guest_nice"s }; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -208,7 +342,7 @@ namespace Cpu { } - auto name_vec = ssplit(name); + auto name_vec = ssplit(name, ' '); if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) { auto cpu_pos = v_index(name_vec, "CPU"s); @@ -224,7 +358,7 @@ namespace Cpu { } else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) { auto cpu_pos = v_index(name_vec, "CPU"s); - if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@") + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1).size() != 1) name = name_vec.at(cpu_pos + 1); else name.clear(); @@ -464,8 +598,8 @@ namespace Cpu { return cpuhz; } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo @@ -538,7 +672,7 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0, ""}; static string auto_sel; - static unordered_flat_map batteries; + static std::unordered_map batteries; //? Get paths to needed files and check for valid values on first run if (batteries.empty() and has_battery) { @@ -807,6 +941,572 @@ namespace Cpu { } } +#ifdef GPU_SUPPORT +namespace Gpu { + //? NVIDIA + namespace Nvml { + bool init() { + if (initialized) return false; + + //? Dynamic loading & linking + //? Try possible library names for libnvidia-ml.so + const array libNvAlts = { + "libnvidia-ml.so", + "libnvidia-ml.so.1", + }; + + for (const auto& l : libNvAlts) { + nvml_dl_handle = dlopen(l, RTLD_LAZY); + if (nvml_dl_handle != nullptr) { + break; + } + } + if (!nvml_dl_handle) { + Logger::info("Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: "s + dlerror()); + return false; + } + + auto load_nvml_sym = [&](const char sym_name[]) { + auto sym = dlsym(nvml_dl_handle, sym_name); + auto err = dlerror(); + if (err != nullptr) { + Logger::error(string("NVML: Couldn't find function ") + sym_name + ": " + err); + return (void*)nullptr; + } else return sym; + }; + + #define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_nvml_sym(#NAME)) == nullptr) return false + + LOAD_SYM(nvmlErrorString); + LOAD_SYM(nvmlInit); + LOAD_SYM(nvmlShutdown); + LOAD_SYM(nvmlDeviceGetCount); + LOAD_SYM(nvmlDeviceGetHandleByIndex); + LOAD_SYM(nvmlDeviceGetName); + LOAD_SYM(nvmlDeviceGetPowerManagementLimit); + LOAD_SYM(nvmlDeviceGetTemperatureThreshold); + LOAD_SYM(nvmlDeviceGetUtilizationRates); + LOAD_SYM(nvmlDeviceGetClockInfo); + LOAD_SYM(nvmlDeviceGetPowerUsage); + LOAD_SYM(nvmlDeviceGetPowerState); + LOAD_SYM(nvmlDeviceGetTemperature); + LOAD_SYM(nvmlDeviceGetMemoryInfo); + LOAD_SYM(nvmlDeviceGetPcieThroughput); + + #undef LOAD_SYM + + //? Function calls + nvmlReturn_t result = nvmlInit(); + if (result != NVML_SUCCESS) { + Logger::debug(std::string("Failed to initialize NVML, NVIDIA GPUs will not be detected: ") + nvmlErrorString(result)); + return false; + } + + //? Device count + result = nvmlDeviceGetCount(&device_count); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get device count: ") + nvmlErrorString(result)); + return false; + } + + if (device_count > 0) { + devices.resize(device_count); + gpus.resize(device_count); + gpu_names.resize(device_count); + + initialized = true; + + //? Check supported functions & get maximums + Nvml::collect<1>(gpus.data()); + + return true; + } else {initialized = true; shutdown(); return false;} + } + + bool shutdown() { + if (!initialized) return false; + nvmlReturn_t result = nvmlShutdown(); + if (NVML_SUCCESS == result) { + initialized = false; + dlclose(nvml_dl_handle); + } else Logger::warning(std::string("Failed to shutdown NVML: ") + nvmlErrorString(result)); + + return !initialized; + } + + template // collect<1> is called in Nvml::init(), and populates gpus.supported_functions + bool collect(gpu_info* gpus_slice) { // raw pointer to vector data, size == device_count + if (!initialized) return false; + + nvmlReturn_t result; + std::thread pcie_tx_thread, pcie_rx_thread; + // DebugTimer nvTotalTimer("Nvidia Total"); + for (unsigned int i = 0; i < device_count; ++i) { + if constexpr(is_init) { + //? Device Handle + result = nvmlDeviceGetHandleByIndex(i, devices.data() + i); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get device handle: ") + nvmlErrorString(result)); + gpus[i].supported_functions = {false, false, false, false, false, false, false, false}; + continue; + } + + //? Device name + char name[NVML_DEVICE_NAME_BUFFER_SIZE]; + result = nvmlDeviceGetName(devices[i], name, NVML_DEVICE_NAME_BUFFER_SIZE); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get device name: ") + nvmlErrorString(result)); + else { + gpu_names[i] = string(name); + for (const auto& brand : {"NVIDIA", "Nvidia", "(R)", "(TM)"}) { + gpu_names[i] = s_replace(gpu_names[i], brand, ""); + } + gpu_names[i] = trim(gpu_names[i]); + } + + //? Power usage + unsigned int max_power; + result = nvmlDeviceGetPowerManagementLimit(devices[i], &max_power); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get maximum GPU power draw, defaulting to 225W: ") + nvmlErrorString(result)); + else { + gpus[i].pwr_max_usage = max_power; // RSMI reports power in microWatts + gpu_pwr_total_max += max_power; + } + + //? Get temp_max + unsigned int temp_max; + result = nvmlDeviceGetTemperatureThreshold(devices[i], NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, &temp_max); + if (result != NVML_SUCCESS) + Logger::warning(std::string("NVML: Failed to get maximum GPU temperature, defaulting to 110°C: ") + nvmlErrorString(result)); + else gpus[i].temp_max = (long long)temp_max; + } + + //? PCIe link speeds, the data collection takes >=20ms each call so they run on separate threads + if (gpus_slice[i].supported_functions.pcie_txrx and (Config::getB("nvml_measure_pcie_speeds") or is_init)) { + pcie_tx_thread = std::thread([gpus_slice, i]() { + unsigned int tx; + nvmlReturn_t result = nvmlDeviceGetPcieThroughput(devices[i], NVML_PCIE_UTIL_TX_BYTES, &tx); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get PCIe TX throughput: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pcie_txrx = false; + } else gpus_slice[i].pcie_tx = (long long)tx; + }); + + pcie_rx_thread = std::thread([gpus_slice, i]() { + unsigned int rx; + nvmlReturn_t result = nvmlDeviceGetPcieThroughput(devices[i], NVML_PCIE_UTIL_RX_BYTES, &rx); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get PCIe RX throughput: ") + nvmlErrorString(result)); + } else gpus_slice[i].pcie_rx = (long long)rx; + }); + } + + // DebugTimer nvTimer("Nv utilization"); + //? GPU & memory utilization + if (gpus_slice[i].supported_functions.gpu_utilization) { + nvmlUtilization_t utilization; + result = nvmlDeviceGetUtilizationRates(devices[i], &utilization); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU utilization: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_utilization = false; + if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; + } else { + gpus_slice[i].gpu_percent.at("gpu-totals").push_back((long long)utilization.gpu); + gpus_slice[i].mem_utilization_percent.push_back((long long)utilization.memory); + } + } + + // nvTimer.stop_rename_reset("Nv clock"); + //? Clock speeds + if (gpus_slice[i].supported_functions.gpu_clock) { + unsigned int gpu_clock; + result = nvmlDeviceGetClockInfo(devices[i], NVML_CLOCK_GRAPHICS, &gpu_clock); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU clock speed: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; + } else gpus_slice[i].gpu_clock_speed = (long long)gpu_clock; + } + + if (gpus_slice[i].supported_functions.mem_clock) { + unsigned int mem_clock; + result = nvmlDeviceGetClockInfo(devices[i], NVML_CLOCK_MEM, &mem_clock); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get VRAM clock speed: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; + } else gpus_slice[i].mem_clock_speed = (long long)mem_clock; + } + + // nvTimer.stop_rename_reset("Nv power"); + //? Power usage & state + if (gpus_slice[i].supported_functions.pwr_usage) { + unsigned int power; + result = nvmlDeviceGetPowerUsage(devices[i], &power); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU power usage: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_usage = false; + } else { + gpus_slice[i].pwr_usage = (long long)power; + gpus_slice[i].gpu_percent.at("gpu-pwr-totals").push_back(clamp((long long)round((double)gpus_slice[i].pwr_usage * 100.0 / (double)gpus_slice[i].pwr_max_usage), 0ll, 100ll)); + } + } + + if (gpus_slice[i].supported_functions.pwr_state) { + nvmlPstates_t pState; + result = nvmlDeviceGetPowerState(devices[i], &pState); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU power state: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_state = false; + } else gpus_slice[i].pwr_state = static_cast(pState); + } + + // nvTimer.stop_rename_reset("Nv temp"); + //? GPU temperature + if (gpus_slice[i].supported_functions.temp_info) { + if (Config::getB("check_temp")) { + unsigned int temp; + nvmlReturn_t result = nvmlDeviceGetTemperature(devices[i], NVML_TEMPERATURE_GPU, &temp); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get GPU temperature: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.temp_info = false; + } else gpus_slice[i].temp.push_back((long long)temp); + } + } + + // nvTimer.stop_rename_reset("Nv mem"); + //? Memory info + if (gpus_slice[i].supported_functions.mem_total) { + nvmlMemory_t memory; + result = nvmlDeviceGetMemoryInfo(devices[i], &memory); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get VRAM info: ") + nvmlErrorString(result)); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_total = false; + if constexpr(is_init) gpus_slice[i].supported_functions.mem_used = false; + } else { + gpus_slice[i].mem_total = memory.total; + gpus_slice[i].mem_used = memory.used; + //gpu.mem_free = memory.free; + + auto used_percent = (long long)round((double)memory.used * 100.0 / (double)memory.total); + gpus_slice[i].gpu_percent.at("gpu-vram-totals").push_back(used_percent); + } + } + + //? TODO: Processes using GPU + /*unsigned int proc_info_len; + nvmlProcessInfo_t* proc_info = 0; + result = nvmlDeviceGetComputeRunningProcesses_v3(device, &proc_info_len, proc_info); + if (result != NVML_SUCCESS) { + Logger::warning(std::string("NVML: Failed to get compute processes: ") + nvmlErrorString(result)); + } else { + for (unsigned int i = 0; i < proc_info_len; ++i) + gpus_slice[i].graphics_processes.push_back({proc_info[i].pid, proc_info[i].usedGpuMemory}); + }*/ + + // nvTimer.stop_rename_reset("Nv pcie thread join"); + //? Join PCIE TX/RX threads + if constexpr(is_init) { // there doesn't seem to be a better way to do this, but this should be fine considering it's just 2 lines + pcie_tx_thread.join(); + pcie_rx_thread.join(); + } else if (gpus_slice[i].supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) { + pcie_tx_thread.join(); + pcie_rx_thread.join(); + } + } + + return true; + } + } + + //? AMD + namespace Rsmi { + bool init() { + if (initialized) return false; + + //? Dynamic loading & linking + #if !defined(RSMI_STATIC) + + //? Try possible library paths and names for librocm_smi64.so + const array libRocAlts = { + "/opt/rocm/lib/librocm_smi64.so", + "librocm_smi64.so", + "librocm_smi64.so.5", // fedora + "librocm_smi64.so.1.0", // debian + }; + + for (const auto& l : libRocAlts) { + rsmi_dl_handle = dlopen(l, RTLD_LAZY); + if (rsmi_dl_handle != nullptr) { + break; + } + } + + if (!rsmi_dl_handle) { + Logger::info("Failed to load librocm_smi64.so, AMD GPUs will not be detected: "s + dlerror()); + return false; + } + + auto load_rsmi_sym = [&](const char sym_name[]) { + auto sym = dlsym(rsmi_dl_handle, sym_name); + auto err = dlerror(); + if (err != nullptr) { + Logger::error(string("ROCm SMI: Couldn't find function ") + sym_name + ": " + err); + return (void*)nullptr; + } else return sym; + }; + + #define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_rsmi_sym(#NAME)) == nullptr) return false + + LOAD_SYM(rsmi_init); + LOAD_SYM(rsmi_shut_down); + LOAD_SYM(rsmi_num_monitor_devices); + LOAD_SYM(rsmi_dev_name_get); + LOAD_SYM(rsmi_dev_power_cap_get); + LOAD_SYM(rsmi_dev_temp_metric_get); + LOAD_SYM(rsmi_dev_busy_percent_get); + LOAD_SYM(rsmi_dev_memory_busy_percent_get); + LOAD_SYM(rsmi_dev_gpu_clk_freq_get); + LOAD_SYM(rsmi_dev_power_ave_get); + LOAD_SYM(rsmi_dev_memory_total_get); + LOAD_SYM(rsmi_dev_memory_usage_get); + LOAD_SYM(rsmi_dev_pci_throughput_get); + + #undef LOAD_SYM + #endif + + //? Function calls + rsmi_status_t result = rsmi_init(0); + if (result != RSMI_STATUS_SUCCESS) { + Logger::debug("Failed to initialize ROCm SMI, AMD GPUs will not be detected"); + return false; + } + + //? Device count + result = rsmi_num_monitor_devices(&device_count); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to fetch number of devices"); + return false; + } + + if (device_count > 0) { + gpus.resize(gpus.size() + device_count); + gpu_names.resize(gpus.size() + device_count); + + initialized = true; + + //? Check supported functions & get maximums + Rsmi::collect<1>(gpus.data() + Nvml::device_count); + + return true; + } else {initialized = true; shutdown(); return false;} + } + + bool shutdown() { + if (!initialized) return false; + if (rsmi_shut_down() == RSMI_STATUS_SUCCESS) { + initialized = false; + #if !defined(RSMI_STATIC) + dlclose(rsmi_dl_handle); + #endif + } else Logger::warning("Failed to shutdown ROCm SMI"); + + return true; + } + + template + bool collect(gpu_info* gpus_slice) { // raw pointer to vector data, size == device_count, offset by Nvml::device_count elements + if (!initialized) return false; + rsmi_status_t result; + + for (uint32_t i = 0; i < device_count; ++i) { + if constexpr(is_init) { + //? Device name + char name[NVML_DEVICE_NAME_BUFFER_SIZE]; // ROCm SMI does not provide a constant for this as far as I can tell, this should be good enough + result = rsmi_dev_name_get(i, name, NVML_DEVICE_NAME_BUFFER_SIZE); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get device name"); + else gpu_names[Nvml::device_count + i] = string(name); + + //? Power usage + uint64_t max_power; + result = rsmi_dev_power_cap_get(i, 0, &max_power); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get maximum GPU power draw, defaulting to 225W"); + else { + gpus_slice[i].pwr_max_usage = (long long)(max_power/1000); // RSMI reports power in microWatts + gpu_pwr_total_max += gpus_slice[i].pwr_max_usage; + } + + //? Get temp_max + int64_t temp_max; + result = rsmi_dev_temp_metric_get(i, RSMI_TEMP_TYPE_EDGE, RSMI_TEMP_MAX, &temp_max); + if (result != RSMI_STATUS_SUCCESS) + Logger::warning("ROCm SMI: Failed to get maximum GPU temperature, defaulting to 110°C"); + else gpus_slice[i].temp_max = (long long)temp_max; + } + + //? GPU utilization + if (gpus_slice[i].supported_functions.gpu_utilization) { + uint32_t utilization; + result = rsmi_dev_busy_percent_get(i, &utilization); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU utilization"); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_utilization = false; + } else gpus_slice[i].gpu_percent.at("gpu-totals").push_back((long long)utilization); + } + + //? Memory utilization + if (gpus_slice[i].supported_functions.mem_utilization) { + uint32_t utilization; + result = rsmi_dev_memory_busy_percent_get(i, &utilization); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM utilization"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; + } else gpus_slice[i].mem_utilization_percent.push_back((long long)utilization); + } + + //? Clock speeds + if (gpus_slice[i].supported_functions.gpu_clock) { + rsmi_frequencies_t frequencies; + result = rsmi_dev_gpu_clk_freq_get(i, RSMI_CLK_TYPE_SYS, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; + } else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + + if (gpus_slice[i].supported_functions.mem_clock) { + rsmi_frequencies_t frequencies; + result = rsmi_dev_gpu_clk_freq_get(i, RSMI_CLK_TYPE_MEM, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; + } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + + //? Power usage & state + if (gpus_slice[i].supported_functions.pwr_usage) { + uint64_t power; + result = rsmi_dev_power_ave_get(i, 0, &power); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU power usage"); + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_usage = false; + } else gpus_slice[i].gpu_percent.at("gpu-pwr-totals").push_back(clamp((long long)round((double)gpus_slice[i].pwr_usage * 100.0 / (double)gpus_slice[i].pwr_max_usage), 0ll, 100ll)); + + if constexpr(is_init) gpus_slice[i].supported_functions.pwr_state = false; + } + + //? GPU temperature + if (gpus_slice[i].supported_functions.temp_info) { + if (Config::getB("check_temp") or is_init) { + int64_t temp; + result = rsmi_dev_temp_metric_get(i, RSMI_TEMP_TYPE_EDGE, RSMI_TEMP_CURRENT, &temp); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU temperature"); + if constexpr(is_init) gpus_slice[i].supported_functions.temp_info = false; + } else gpus_slice[i].temp.push_back((long long)temp/1000); + } + } + + //? Memory info + if (gpus_slice[i].supported_functions.mem_total) { + uint64_t total; + result = rsmi_dev_memory_total_get(i, RSMI_MEM_TYPE_VRAM, &total); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get total VRAM"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_total = false; + } else gpus_slice[i].mem_total = total; + } + + if (gpus_slice[i].supported_functions.mem_used) { + uint64_t used; + result = rsmi_dev_memory_usage_get(i, RSMI_MEM_TYPE_VRAM, &used); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM usage"); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_used = false; + } else { + gpus_slice[i].mem_used = used; + if (gpus_slice[i].supported_functions.mem_total) + gpus_slice[i].gpu_percent.at("gpu-vram-totals").push_back((long long)round((double)used * 100.0 / (double)gpus_slice[i].mem_total)); + } + } + + //? PCIe link speeds + if (gpus_slice[i].supported_functions.pcie_txrx) { + uint64_t tx, rx; + result = rsmi_dev_pci_throughput_get(i, &tx, &rx, 0); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get PCIe throughput"); + if constexpr(is_init) gpus_slice[i].supported_functions.pcie_txrx = false; + } else { + gpus_slice[i].pcie_tx = (long long)tx; + gpus_slice[i].pcie_rx = (long long)rx; + } + } + } + + return true; + } + } + + // TODO: Intel + + //? Collect data from GPU-specific libraries + auto collect(bool no_update) -> vector& { + if (Runner::stopping or (no_update and not gpus.empty())) return gpus; + + // DebugTimer gpu_timer("GPU Total"); + + //* Collect data + Nvml::collect<0>(gpus.data()); // raw pointer to vector data, size == Nvml::device_count + Rsmi::collect<0>(gpus.data() + Nvml::device_count); // size = Rsmi::device_count + + //* Calculate average usage + long long avg = 0; + long long mem_usage_total = 0; + long long mem_total = 0; + long long pwr_total = 0; + for (auto& gpu : gpus) { + if (gpu.supported_functions.gpu_utilization) + avg += gpu.gpu_percent.at("gpu-totals").back(); + if (gpu.supported_functions.mem_used) + mem_usage_total += gpu.mem_used; + if (gpu.supported_functions.mem_total) + mem_total += gpu.mem_total; + if (gpu.supported_functions.pwr_usage) + mem_total += gpu.pwr_usage; + + //* Trim vectors if there are more values than needed for graphs + if (width != 0) { + //? GPU & memory utilization + while (cmp_greater(gpu.gpu_percent.at("gpu-totals").size(), width * 2)) gpu.gpu_percent.at("gpu-totals").pop_front(); + while (cmp_greater(gpu.mem_utilization_percent.size(), width)) gpu.mem_utilization_percent.pop_front(); + //? Power usage + while (cmp_greater(gpu.gpu_percent.at("gpu-pwr-totals").size(), width)) gpu.gpu_percent.at("gpu-pwr-totals").pop_front(); + //? Temperature + while (cmp_greater(gpu.temp.size(), 18)) gpu.temp.pop_front(); + //? Memory usage + while (cmp_greater(gpu.gpu_percent.at("gpu-vram-totals").size(), width/2)) gpu.gpu_percent.at("gpu-vram-totals").pop_front(); + } + } + + shared_gpu_percent.at("gpu-average").push_back(avg / gpus.size()); + if (mem_total != 0) + shared_gpu_percent.at("gpu-vram-total").push_back(mem_usage_total / mem_total); + if (gpu_pwr_total_max != 0) + shared_gpu_percent.at("gpu-pwr-total").push_back(pwr_total / gpu_pwr_total_max); + + if (width != 0) { + while (cmp_greater(shared_gpu_percent.at("gpu-average").size(), width * 2)) shared_gpu_percent.at("gpu-average").pop_front(); + while (cmp_greater(shared_gpu_percent.at("gpu-pwr-total").size(), width * 2)) shared_gpu_percent.at("gpu-pwr-total").pop_front(); + while (cmp_greater(shared_gpu_percent.at("gpu-vram-total").size(), width * 2)) shared_gpu_percent.at("gpu-vram-total").pop_front(); + } + + return gpus; + } +} +#endif + namespace Mem { bool has_swap{}; // defaults to false vector fstab; @@ -939,6 +1639,7 @@ namespace Mem { auto only_physical = Config::getB("only_physical"); auto zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; + static std::unordered_map>> disks_stats_promises; ifstream diskread; vector filter; @@ -1078,31 +1779,47 @@ namespace Mem { diskread.close(); //? Get disk/partition stats - bool new_ignored = false; - for (auto& [mountpoint, disk] : disks) { - if (std::error_code ec; not fs::exists(mountpoint, ec) or v_contains(ignore_list, mountpoint)) continue; - struct statvfs vfs; - if (statvfs(mountpoint.c_str(), &vfs) < 0) { - Logger::warning("Failed to get disk/partition stats for mount \""+ mountpoint + "\" with statvfs error code: " + to_string(errno) + ". Ignoring..."); - ignore_list.push_back(mountpoint); - new_ignored = true; + for (auto it = disks.begin(); it != disks.end(); ) { + auto &[mountpoint, disk] = *it; + if (v_contains(ignore_list, mountpoint) or disk.name == "swap") { + it = disks.erase(it); continue; } - disk.total = vfs.f_blocks * vfs.f_frsize; - disk.free = (free_priv ? vfs.f_bfree : vfs.f_bavail) * vfs.f_frsize; - disk.used = disk.total - disk.free; - disk.used_percent = round((double)disk.used * 100 / disk.total); - disk.free_percent = 100 - disk.used_percent; - } - - //? Remove any problematic disks added to the ignore_list - if (new_ignored) { - for (auto it = disks.begin(); it != disks.end();) { - if (v_contains(ignore_list, it->first)) + if(auto promises_it = disks_stats_promises.find(mountpoint); promises_it != disks_stats_promises.end()){ + auto& promise = promises_it->second; + if(promise.valid() && + promise.wait_for(0s) == std::future_status::timeout) { + ++it; + continue; + } + auto promise_res = promises_it->second.get(); + if(promise_res.second != -1){ + ignore_list.push_back(mountpoint); + Logger::warning("Failed to get disk/partition stats for mount \""+ mountpoint + "\" with statvfs error code: " + to_string(promise_res.second) + ". Ignoring..."); it = disks.erase(it); - else - it++; + continue; + } + auto &updated_stats = promise_res.first; + disk.total = updated_stats.total; + disk.free = updated_stats.free; + disk.used = updated_stats.used; + disk.used_percent = updated_stats.used_percent; + disk.free_percent = updated_stats.free_percent; } + disks_stats_promises[mountpoint] = async(std::launch::async, [mountpoint, &free_priv]() -> pair { + struct statvfs vfs; + disk_info disk; + if (statvfs(mountpoint.c_str(), &vfs) < 0) { + return pair{disk, errno}; + } + disk.total = vfs.f_blocks * vfs.f_frsize; + disk.free = (free_priv ? vfs.f_bfree : vfs.f_bavail) * vfs.f_frsize; + disk.used = disk.total - disk.free; + disk.used_percent = round((double)disk.used * 100 / disk.total); + disk.free_percent = 100 - disk.used_percent; + return pair{disk, -1}; + }); + ++it; } //? Setup disks order in UI and add swap if enabled @@ -1245,29 +1962,32 @@ namespace Mem { } // looking through all files that start with 'objset' to find the one containing `device_name` object stats - for (const auto& file: fs::directory_iterator(zfs_pool_stat_path)) { - filename = file.path().filename(); - if (filename.starts_with("objset")) { - filestream.open(file.path()); - if (filestream.good()) { - // skip first two lines - for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); - // skip characters until '7' is reached, indicating data type 7, next value will be object name - filestream.ignore(numeric_limits::max(), '7'); - filestream >> name_compare; - if (name_compare == device_name) { - filestream.close(); - if (access(file.path().c_str(), R_OK) == 0) { - return file.path(); - } else { - Logger::debug("Can't access file: " + file.path().string()); - return ""; + try { + for (const auto& file: fs::directory_iterator(zfs_pool_stat_path)) { + filename = file.path().filename(); + if (filename.starts_with("objset")) { + filestream.open(file.path()); + if (filestream.good()) { + // skip first two lines + for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); + // skip characters until '7' is reached, indicating data type 7, next value will be object name + filestream.ignore(numeric_limits::max(), '7'); + filestream >> name_compare; + if (name_compare == device_name) { + filestream.close(); + if (access(file.path().c_str(), R_OK) == 0) { + return file.path(); + } else { + Logger::debug("Can't access file: " + file.path().string()); + return ""; + } } } + filestream.close(); } - filestream.close(); } } + catch (fs::filesystem_error& e) {} Logger::debug("Could not read directory: " + zfs_pool_stat_path.string()); return ""; @@ -1354,13 +2074,13 @@ namespace Mem { } namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors{}; // defaults to 0 - unordered_flat_map graph_max = { {"download", {}}, {"upload", {}} }; - unordered_flat_map> max_count = { {"download", {}}, {"upload", {}} }; + std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; + std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; uint64_t timestamp{}; // defaults to 0 @@ -1499,7 +2219,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1569,7 +2288,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev{}; // defaults to false @@ -1584,7 +2303,7 @@ namespace Proc { detail_container detailed; constexpr size_t KTHREADD = 2; - static robin_hood::unordered_set kernels_procs = {KTHREADD}; + static std::unordered_set kernels_procs = {KTHREADD}; //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { @@ -2070,6 +2789,6 @@ namespace Tools { catch (const std::invalid_argument&) {} catch (const std::out_of_range&) {} } - throw std::runtime_error("Failed get uptime from from " + string{Shared::procPath} + "/uptime"); + throw std::runtime_error("Failed to get uptime from " + string{Shared::procPath} + "/uptime"); } } diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 26a93d3..76b63c0 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -44,6 +45,7 @@ tab-size = 4 #include // for inet_ntop #include #include +#include #include #include @@ -56,7 +58,9 @@ tab-size = 4 #include "../btop_shared.hpp" #include "../btop_tools.hpp" +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" +#endif #include "smc.hpp" using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; @@ -70,7 +74,7 @@ using namespace Tools; namespace Cpu { vector core_old_totals; vector core_old_idles; - vector available_fields = {"total"}; + vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; bool got_sensors = false, cpu_temp_only = false; @@ -95,7 +99,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -191,7 +195,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -236,7 +240,7 @@ namespace Cpu { name += n + ' '; } name.pop_back(); - for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) { + for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Apple", "Core"}) { name = s_replace(name, replace, ""); name = s_replace(name, " ", " "); } @@ -250,6 +254,7 @@ namespace Cpu { Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp"))); got_sensors = false; if (Config::getB("show_coretemp") and Config::getB("check_temp")) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; if (sensors.getSensors() > 0) { Logger::debug("M1 sensors found"); @@ -257,6 +262,7 @@ namespace Cpu { cpu_temp_only = true; macM1 = true; } else { +#endif // try SMC (intel) Logger::debug("checking intel"); SMCConnection smcCon; @@ -281,7 +287,9 @@ namespace Cpu { // ignore, we don't have temp got_sensors = false; } +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 } +#endif } return got_sensors; } @@ -290,11 +298,12 @@ namespace Cpu { current_cpu.temp_max = 95; // we have no idea how to get the critical temp try { if (macM1) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 ThermalSensors sensors; current_cpu.temp.at(0).push_back(sensors.getSensors()); if (current_cpu.temp.at(0).size() > 20) current_cpu.temp.at(0).pop_front(); - +#endif } else { SMCConnection smcCon; int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount; @@ -329,8 +338,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3); } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; natural_t cpu_count; @@ -591,7 +600,7 @@ namespace Mem { io_object_t &object; }; - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { io_registry_entry_t drive; io_iterator_t drive_list; @@ -708,7 +717,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -821,13 +830,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -904,7 +913,7 @@ namespace Net { } // else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -978,7 +987,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1049,7 +1057,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; diff --git a/src/osx/sensors.cpp b/src/osx/sensors.cpp index 965d9a9..bee8978 100644 --- a/src/osx/sensors.cpp +++ b/src/osx/sensors.cpp @@ -16,6 +16,8 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 #include "sensors.hpp" #include @@ -109,3 +111,4 @@ long long Cpu::ThermalSensors::getSensors() { if (temps.empty()) return 0ll; return round(std::accumulate(temps.begin(), temps.end(), 0ll) / temps.size()); } +#endif diff --git a/src/osx/sensors.hpp b/src/osx/sensors.hpp index 08581e1..fcb28b0 100644 --- a/src/osx/sensors.hpp +++ b/src/osx/sensors.hpp @@ -16,9 +16,12 @@ indent = tab tab-size = 4 */ +#include +#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 namespace Cpu { class ThermalSensors { public: long long getSensors(); }; } // namespace Cpu +#endif diff --git a/themes/elementarish.theme b/themes/elementarish.theme index 8d5b9d1..0fe5398 100644 --- a/themes/elementarish.theme +++ b/themes/elementarish.theme @@ -3,7 +3,7 @@ # By: Dennis Mayr # Main bg -theme[main_bg]="#2b2b2b" +theme[main_bg]="#333333" # Main text color theme[main_fg]="#eee8d5" @@ -12,71 +12,71 @@ theme[main_fg]="#eee8d5" theme[title]="#eee8d5" # Higlight color for keyboard shortcuts -theme[hi_fg]="#dc322f" +theme[hi_fg]="#d1302c" # Background color of selected item in processes box -theme[selected_bg]="#268bd2" +theme[selected_bg]="#268ad0" # Foreground color of selected item in processes box theme[selected_fg]="#eee8d5" # Color of inactive/disabled text -theme[inactive_fg]="#586e75" +theme[inactive_fg]="#657b83" # Misc colors for processes box including mini cpu graphs, details memory graph and details status text -theme[proc_misc]="#268bd2" +theme[proc_misc]="#268ad0" # Cpu box outline color -theme[cpu_box]="#586e75" +theme[cpu_box]="#657b83" # Memory/disks box outline color -theme[mem_box]="#586e75" +theme[mem_box]="#657b83" # Net up/down box outline color -theme[net_box]="#586e75" +theme[net_box]="#657b83" # Processes box outline color -theme[proc_box]="#586e75" +theme[proc_box]="#657b83" # Box divider line and small boxes line color -theme[div_line]="#586e75" +theme[div_line]="#657b83" # Temperature graph colors theme[temp_start]="#859900" -theme[temp_mid]="#b58901" -theme[temp_end]="#dc322f" +theme[temp_mid]="#b28602" +theme[temp_end]="#d1302c" # CPU graph colors theme[cpu_start]="#859900" -theme[cpu_mid]="#b58901" -theme[cpu_end]="#dc322f" +theme[cpu_mid]="#b28602" +theme[cpu_end]="#d1302c" # Mem/Disk free meter -theme[free_start]="#268bd2" +theme[free_start]="#268ad0" theme[free_mid]="#6c71c4" theme[free_end]="#2a9d95" # Mem/Disk cached meter -theme[cached_start]="#268bd2" +theme[cached_start]="#268ad0" theme[cached_mid]="#6c71c4" -theme[cached_end]="#dc322f" +theme[cached_end]="#d1302c" # Mem/Disk available meter -theme[available_start]="#268bd2" +theme[available_start]="#268ad0" theme[available_mid]="#6c71c4" -theme[available_end]="#dc322f" +theme[available_end]="#d1302c" # Mem/Disk used meter theme[used_start]="#859900" -theme[used_mid]="#b58901" -theme[used_end]="#dc322f" +theme[used_mid]="#b28602" +theme[used_end]="#d1302c" # Download graph colors -theme[download_start]="#268bd2" +theme[download_start]="#268ad0" theme[download_mid]="#6c71c4" -theme[download_end]="#dc322f" +theme[download_end]="#d1302c" # Upload graph colors -theme[upload_start]="#268bd2" +theme[upload_start]="#268ad0" theme[upload_mid]="#6c71c4" -theme[upload_end]="#dc322f" +theme[upload_end]="#d1302c" diff --git a/themes/horizon.theme b/themes/horizon.theme new file mode 100644 index 0000000..15c2d0e --- /dev/null +++ b/themes/horizon.theme @@ -0,0 +1,86 @@ +# All graphs and meters can be gradients +# For single color graphs leave "mid" and "end" variable empty. +# Use "start" and "end" variables for two color gradient +# Use "start", "mid" and "end" for three color gradient + +# Main background, empty for terminal default, need to be empty if you want transparent background +theme[main_bg]="#1C1E26" + +# Main text color +theme[main_fg]="#f8f8f2" + +# Title color for boxes +theme[title]="#f8f8f2" + +# Highlight color for keyboard shortcuts +theme[hi_fg]="#B877DB" + +# Background color of selected items +theme[selected_bg]="#282b37" + +# Foreground color of selected items +theme[selected_fg]="#f8f8f2" + +# Color of inactive/disabled text +theme[inactive_fg]="#272e33" + +# Color of text appearing on top of graphs, i.e uptime and current network graph scaling +theme[graph_text]="#f8f8f2" + +# Misc colors for processes box including mini cpu graphs, details memory graph and details status text +theme[proc_misc]="#27D796" + +# Cpu box outline color +theme[cpu_box]="#B877DB" + +# Memory/disks box outline color +theme[mem_box]="#27D796" + +# Net up/down box outline color +theme[net_box]="#E95678" + +# Processes box outline color +theme[proc_box]="#25B2BC" + +# Box divider line and small boxes line color +theme[div_line]="#272e33" + +# Temperature graph colors +theme[temp_start]="#27D796" +theme[temp_mid]="#FAC29A" +theme[temp_end]="#E95678" + +# CPU graph colors +theme[cpu_start]="#27D796" +theme[cpu_mid]="#FAC29A" +theme[cpu_end]="#E95678" + +# Mem/Disk free meter +theme[free_start]="#E95678" +theme[free_mid]="#FAC29A" +theme[free_end]="#27D796" + +# Mem/Disk cached meter +theme[cached_start]="#27D796" +theme[cached_mid]="#FAC29A" +theme[cached_end]="#E95678" + +# Mem/Disk available meter +theme[available_start]="#27D796" +theme[available_mid]="#FAC29A" +theme[available_end]="#E95678" + +# Mem/Disk used meter +theme[used_start]="#27D796" +theme[used_mid]="#FAC29A" +theme[used_end]="#E95678" + +# Download graph colors +theme[download_start]="#27D796" +theme[download_mid]="#FAC29A" +theme[download_end]="#E95678" + +# Upload graph colors +theme[upload_start]="#27D796" +theme[upload_mid]="#FAC29A" +theme[upload_end]="#E95678" From 4637ed399134083e429fd63a002b897d9f2cb5ee Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 15:37:36 +0100 Subject: [PATCH 35/91] Change robin_hood maps to std::unordered_map --- src/openbsd/btop_collect.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index 1d4e774..df35662 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -104,7 +104,7 @@ namespace Cpu { string cpu_sensor; vector core_sensors; - unordered_flat_map core_mapping; + std::unordered_map core_mapping; } // namespace Cpu namespace Mem { @@ -206,7 +206,7 @@ namespace Cpu { const array time_names = {"user", "nice", "system", "idle"}; - unordered_flat_map cpu_old = { + std::unordered_map cpu_old = { {"totals", 0}, {"idles", 0}, {"user", 0}, @@ -342,8 +342,8 @@ namespace Cpu { return std::to_string(freq / 1000.0 ).substr(0, 3); // seems to be in MHz } - auto get_core_mapping() -> unordered_flat_map { - unordered_flat_map core_map; + auto get_core_mapping() -> std::unordered_map { + std::unordered_map core_map; if (cpu_temp_only) return core_map; for (long i = 0; i < Shared::coreCount; i++) { @@ -428,7 +428,7 @@ namespace Cpu { if (getloadavg(cpu.load_avg.data(), cpu.load_avg.size()) < 0) { Logger::error("failed to get load averages"); } - + auto cp_time = std::unique_ptr{ new struct cpustats[Shared::coreCount] }; @@ -560,7 +560,7 @@ namespace Mem { while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); } - void collect_disk(unordered_flat_map &disks, unordered_flat_map &mapping) { + void collect_disk(std::unordered_map &disks, std::unordered_map &mapping) { uint64_t total_bytes_read = 0; uint64_t total_bytes_write = 0; @@ -654,7 +654,7 @@ namespace Mem { } if (show_disks) { - unordered_flat_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint + std::unordered_map mapping; // keep mapping from device -> mountpoint, since IOKit doesn't give us the mountpoint double uptime = system_uptime(); auto &disks_filter = Config::getS("disks_filter"); bool filter_exclude = false; @@ -770,13 +770,13 @@ namespace Mem { } // namespace Mem namespace Net { - unordered_flat_map current_net; + std::unordered_map current_net; net_info empty_net = {}; vector interfaces; string selected_iface; int errors = 0; - unordered_flat_map graph_max = {{"download", {}}, {"upload", {}}}; - unordered_flat_map> max_count = {{"download", {}}, {"upload", {}}}; + std::unordered_map graph_max = {{"download", {}}, {"upload", {}}}; + std::unordered_map> max_count = {{"download", {}}, {"upload", {}}}; bool rescale = true; uint64_t timestamp = 0; @@ -855,7 +855,7 @@ namespace Net { } //else, ignoring family==AF_LINK (see man 3 getifaddrs) } - unordered_flat_map> ifstats; + std::unordered_map> ifstats; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; size_t len; if (sysctl(mib, 6, nullptr, &len, nullptr, 0) < 0) { @@ -929,7 +929,6 @@ namespace Net { else it++; } - net.compact(); } timestamp = new_timestamp; @@ -1000,7 +999,7 @@ namespace Net { namespace Proc { vector current_procs; - unordered_flat_map uid_user; + std::unordered_map uid_user; string current_sort; string current_filter; bool current_rev = false; From c8d0322c3ad3f161d719e8d7effb567fd4183ac9 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 16:05:12 +0100 Subject: [PATCH 36/91] Add Continuous Build OpenBSD --- .../workflows/continuous-build-freebsd.yml | 2 + .github/workflows/continuous-build-linux.yml | 2 + .github/workflows/continuous-build-macos.yml | 8 ++- .../workflows/continuous-build-openbsd.yml | 58 +++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/continuous-build-openbsd.yml diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 041133f..d06190f 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-freebsd.yml' diff --git a/.github/workflows/continuous-build-linux.yml b/.github/workflows/continuous-build-linux.yml index 3ef236c..39de640 100644 --- a/.github/workflows/continuous-build-linux.yml +++ b/.github/workflows/continuous-build-linux.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-linux.yml' diff --git a/.github/workflows/continuous-build-macos.yml b/.github/workflows/continuous-build-macos.yml index 717e9a7..c8915dd 100644 --- a/.github/workflows/continuous-build-macos.yml +++ b/.github/workflows/continuous-build-macos.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -21,6 +22,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-macos.yml' @@ -44,18 +46,18 @@ jobs: with: name: btop-x86_64-macos11-BigSur path: 'bin/*' - + build-macos12: runs-on: macos-12 steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: latest-stable - uses: actions/checkout@v3 with: submodules: recursive - + - name: Compile run: | make CXX=g++-12 ARCH=x86_64 STATIC=true STRIP=true diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml new file mode 100644 index 0000000..12324cd --- /dev/null +++ b/.github/workflows/continuous-build-openbsd.yml @@ -0,0 +1,58 @@ +name: Continuous Build OpenBSD + +on: + workflow_dispatch: + push: + branches: + - main + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-openbsd.yml' + pull_request: + branches: + - main + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-openbsd.yml' + +jobs: + build-freebsd: + runs-on: ubuntu-22.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Compile + uses: vmactions/openbsd-vm@v1 + with: + release: '7.4' + usesh: true + prepare: | + pkg_add gmake gcc%11 g++%11 coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop + run: | + gmake CXX=eg++ STATIC=true STRIP=true + GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") + mv bin/btop bin/btop-GCC11-"$GIT_HASH" + ls -alh bin + + - uses: actions/upload-artifact@v3 + with: + name: btop-x86_64-openbsd-7.4 + path: 'bin/*' + if-no-files-found: error + From eb716b31ab58fb9425bd04f3b1694d551d6f27f7 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 16:06:35 +0100 Subject: [PATCH 37/91] Fix name of job in build --- .github/workflows/continuous-build-openbsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml index 12324cd..afb1a93 100644 --- a/.github/workflows/continuous-build-openbsd.yml +++ b/.github/workflows/continuous-build-openbsd.yml @@ -28,7 +28,7 @@ on: - '.github/workflows/continuous-build-openbsd.yml' jobs: - build-freebsd: + build-openbsd: runs-on: ubuntu-22.04 timeout-minutes: 20 steps: From f4154608390890182a6ea646b9e2bb8968f1b08e Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 16:22:46 +0100 Subject: [PATCH 38/91] Add compile instructions for OpenBSD --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/README.md b/README.md index f220e86..fef49c2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [Compilation OpenBSD](#compilation-openbsd) * [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) * [Configurability](#configurability) @@ -868,6 +869,100 @@ Also needs a UTF8 locale and a font that covers: +## Compilation OpenBSD + + Requires at least GCC 10. + + Note that GNU make (`gmake`) is required to compile on OpenBSD. + +
+ + +### With gmake + + +1. **Install dependencies** + + ```bash + pkg_add gmake gcc%11 g++%11 coreutils git + ``` + +2. **Clone repository** + + ```bash + git clone https://github.com/aristocratos/btop.git + cd btop + ``` + +3. **Compile** + + ```bash + gmake CXX=eg++ + ``` + + Options for make: + + | Flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `VERBOSE=true` | To display full compiler/linker commands | + | `STATIC=true` | For static compilation (only libgcc and libstdc++) | + | `QUIET=true` | For less verbose output | + | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | + | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | + | `ARCH=` | To manually set the target architecture | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manualy set which compiler to use | + + Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. + +4. **Install** + + ```bash + sudo gmake install + ``` + + Append `PREFIX=/target/dir` to set target, default: `/usr/local` + + Notice! Only use "sudo" when installing to a NON user owned directory. + +5. **(Recommended) Set suid bit to make btop always run as root (or other user)** + + ```bash + sudo gmake setuid + ``` + + No need for `sudo` to see information for non user owned processes and to enable signal sending to any process. + + Run after make install and use same PREFIX if any was used at install. + + Set `SU_USER` and `SU_GROUP` to select user and group, default is `root` and `wheel` + +* **Uninstall** + + ```bash + sudo gmake uninstall + ``` + +* **Remove any object files from source dir** + + ```bash + gmake clean + ``` + +* **Remove all object files, binaries and created directories in source dir** + + ```bash + gmake distclean + ``` + +* **Show help** + + ```bash + gmake help + ``` + +
+ ## Installing the snap [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) From 35672546536070931455214abf6c22e2e000fdbe Mon Sep 17 00:00:00 2001 From: aristocratos Date: Tue, 2 Jan 2024 16:54:52 +0100 Subject: [PATCH 39/91] Add build status for OpenBSD --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fef49c2..fa67b70 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![macOS](https://img.shields.io/badge/-OSX-black?logo=apple) ![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd) +![OpenBSD](https://img.shields.io/badge/-OpenBSD-black?logo=openbsd) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow) ![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green) ![latest_release](https://img.shields.io/github/v/tag/aristocratos/btop?label=release) @@ -17,6 +18,7 @@ [![Continuous Build Linux](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-linux.yml) [![Continuous Build macOS](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-macos.yml) [![Continuous Build FreeBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-freebsd.yml) +[![Continuous Build OpenBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-openbsd.yml) ## Index From 85d75038e949e3aa07c9ca6c0544502b445981e1 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Wed, 13 Sep 2023 23:17:06 +0200 Subject: [PATCH 40/91] Remove detailed compiler check Currently the Makefile tries to pick the latest version of g++ and overrides CXX when found. Since we give an error if the version of the compiler is not sufficient, we can remove this check. It's now possible to specify an older version (on purpose) and new compiler version don't need to be added manually. --- Makefile | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 9f4db2e..36e2ea1 100644 --- a/Makefile +++ b/Makefile @@ -73,34 +73,9 @@ ifeq ($(CXX_IS_CLANG),true) ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt $(MIN_CLANG_VERSION); echo $$?),0) CLANG_WORKS := true endif -endif -ifeq ($(CLANG_WORKS),false) - #? Try to find a newer GCC version - ifeq ($(shell command -v g++-13 >/dev/null; echo $$?),0) - CXX := g++-13 - else ifeq ($(shell command -v g++13 >/dev/null; echo $$?),0) - CXX := g++13 - else ifeq ($(shell command -v g++-12 >/dev/null; echo $$?),0) - CXX := g++-12 - else ifeq ($(shell command -v g++12 >/dev/null; echo $$?),0) - CXX := g++12 - else ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0) - CXX := g++-11 - else ifeq ($(shell command -v g++11 >/dev/null; echo $$?),0) - CXX := g++11 - else ifeq ($(shell command -v g++ >/dev/null; echo $$?),0) - CXX := g++ - else ifeq ($(shell command -v eg++ >/dev/null; echo $$?),0) - CXX := eg++ - else - GCC_NOT_FOUND := true - endif - ifndef GCC_NOT_FOUND - override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0) - override CXX_VERSION_MAJOR := $(shell echo $(CXX_VERSION) | cut -d '.' -f 1) - ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0) - GCC_WORKS := true - endif +else + ifneq ($(shell test $(CXX_VERSION_MAJOR) -lt 10; echo $$?),0) + GCC_WORKS := true endif endif From ea53d48667bf36df8c1550f3f5aac574bd1ca5de Mon Sep 17 00:00:00 2001 From: "Jakob P. Liljenberg" Date: Wed, 3 Jan 2024 16:01:13 +0100 Subject: [PATCH 41/91] Update snapcraft.yaml to fix compile --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f6b2729..2e76ba5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -45,6 +45,7 @@ parts: source-type: git plugin: make make-parameters: + - CXX=g++-11 - PREFIX=/usr/local - STATIC=true - ADDFLAGS="-D SNAPPED" From ff8352fdcdc08d32494b5873bb85164f2054825a Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 23 Oct 2023 15:18:28 +0200 Subject: [PATCH 42/91] Improved error handling when determining the config directory More verbose error printing when a directory is e.g. not readable Also allow the use of read only configurations with disabled logging and persistent configuration --- src/btop.cpp | 30 ++++++++------------ src/btop_config.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++- src/btop_config.hpp | 5 +++- src/btop_tools.cpp | 1 + 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 48d0482..6908dfa 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -829,29 +829,23 @@ int main(int argc, char **argv) { //? Call argument parser if launched with arguments if (argc > 1) argumentParser(argc, argv); - //? Setup paths for config, log and user themes - for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { - if (std::getenv(env) != nullptr and access(std::getenv(env), W_OK) != -1) { - Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); - break; - } - } - if (Config::conf_dir.empty()) { - fmt::println("WARNING: Could not get path user HOME folder.\n" - "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this."); - } - else { - if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) { - fmt::println("WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n" - "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this."); - } - else { + { + const auto config_dir = Config::get_config_dir(); + if (config_dir.has_value()) { + Config::conf_dir = config_dir.value(); Config::conf_file = Config::conf_dir / "btop.conf"; Logger::logfile = Config::conf_dir / "btop.log"; Theme::user_theme_dir = Config::conf_dir / "themes"; - if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear(); + + // 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; diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6ddfd43..535d8c8 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -24,6 +24,7 @@ tab-size = 4 #include #include +#include #include "btop_config.hpp" #include "btop_shared.hpp" @@ -320,6 +321,65 @@ namespace Config { }; std::unordered_map intsTmp; + // Returns a valid config dir or an empty optional + // The config dir might be read only, a warning is printed, but a path is returned anyway + [[nodiscard]] std::optional get_config_dir() noexcept { + fs::path config_dir; + { + std::error_code error; + if (const auto xdg_config_home = std::getenv("XDG_CONFIG_HOME"); xdg_config_home != nullptr) { + if (fs::exists(xdg_config_home, error)) { + config_dir = fs::path(xdg_config_home) / "btop"; + } + } else if (const auto home = std::getenv("HOME"); home != nullptr) { + error.clear(); + if (fs::exists(home, error)) { + config_dir = fs::path(home) / ".config" / "btop"; + } + if (error) { + fmt::print(stderr, "\033[0;31mWarning: \033[0m{} could not be accessed: {}\n", config_dir.string(), error.message()); + config_dir = ""; + } + } + } + + // FIXME: This warnings can be noisy if the user deliberately has a non-writable config dir + // offer an alternative | disable messages by default | disable messages if config dir is not writable | disable messages with a flag + // FIXME: Make happy path not branch + if (not config_dir.empty()) { + std::error_code error; + if (fs::exists(config_dir, error)) { + if (fs::is_directory(config_dir, error)) { + struct statvfs stats {}; + if ((fs::status(config_dir, error).permissions() & fs::perms::owner_write) == fs::perms::owner_write and + statvfs(config_dir.c_str(), &stats) == 0 and (stats.f_flag & ST_RDONLY) == 0) { + return config_dir; + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not writable\n", fs::absolute(config_dir).string()); + // If the config is readable we can still use the provided config, but changes will not be persistent + if ((fs::status(config_dir, error).permissions() & fs::perms::owner_read) == fs::perms::owner_read) { + fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n"); + return config_dir; + } + } + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` is not a directory\n", fs::absolute(config_dir).string()); + } + } else { + // Doesn't exist + if (fs::create_directories(config_dir, error)) { + return config_dir; + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0m`{}` could not be created: {}\n", fs::absolute(config_dir).string(), error.message()); + } + } + } else { + fmt::print(stderr, "\033[0;31mWarning: \033[0mCould not determine config path: Make sure `$XDG_CONFIG_HOME` or `$HOME` is set\n"); + } + fmt::print(stderr, "\033[0;31mWarning: \033[0mLogging is disabled, config changes are not persistent\n"); + return {}; + } + bool _locked(const std::string_view name) { atomic_wait(writelock, true); if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end()) @@ -594,12 +654,17 @@ namespace Config { } void load(const fs::path& conf_file, vector& load_warnings) { + std::error_code error; if (conf_file.empty()) return; - else if (not fs::exists(conf_file)) { + else if (not fs::exists(conf_file, error)) { write_new = true; return; } + if (error) { + return; + } + std::ifstream cread(conf_file); if (cread.good()) { vector valid_names; diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 2b586af..b93509f 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -18,9 +18,10 @@ tab-size = 4 #pragma once +#include +#include #include #include -#include #include @@ -57,6 +58,8 @@ namespace Config { extern vector available_batteries; extern int current_preset; + [[nodiscard]] std::optional get_config_dir() noexcept; + //* Check if string only contains space separated valid names for boxes bool check_boxes(const string& boxes); diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index e6ecbd8..967e2f1 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -669,6 +669,7 @@ namespace Logger { lose_priv neutered{}; std::error_code ec; try { + // NOTE: `exist()` could throw but since we return with an empty logfile we don't care if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { auto old_log = logfile; old_log += ".1"; From b124db78c071af5cae047549559f801d05438d71 Mon Sep 17 00:00:00 2001 From: nobounce Date: Tue, 3 Oct 2023 13:32:28 +0200 Subject: [PATCH 43/91] Don't pass int by reference --- src/btop.cpp | 2 +- src/btop_config.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 6a57240..6c7eb99 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -112,7 +112,7 @@ namespace Global { } //* A simple argument parser -void argumentParser(const int& argc, char **argv) { +void argumentParser(const int argc, char **argv) { for(int i = 1; i < argc; i++) { const string argument = argv[i]; if (is_in(argument, "-h", "--help")) { diff --git a/src/btop_config.hpp b/src/btop_config.hpp index b93509f..8d1d579 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -97,7 +97,7 @@ namespace Config { } //* Set config key to int - inline void set(const std::string_view name, const int& value) { + inline void set(const std::string_view name, const int value) { if (_locked(name)) intsTmp.insert_or_assign(name, value); else ints.at(name) = value; } From 1e6bf18c65a9083b30b7da339d2636eda4939c4a Mon Sep 17 00:00:00 2001 From: nobounce Date: Tue, 3 Oct 2023 13:54:28 +0200 Subject: [PATCH 44/91] Add flag to set the refresh rate from command line --- src/btop.cpp | 22 +++++++++++++++++++++- src/btop_config.cpp | 4 ++-- src/btop_config.hpp | 2 ++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 6c7eb99..9ad5012 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -16,6 +16,7 @@ indent = tab tab-size = 4 */ +#include #include #include #include @@ -51,6 +52,7 @@ tab-size = 4 #include "btop_theme.hpp" #include "btop_draw.hpp" #include "btop_menu.hpp" +#include "fmt/core.h" using std::atomic; using std::cout; @@ -109,6 +111,7 @@ namespace Global { bool arg_tty{}; // defaults to false bool arg_low_color{}; // defaults to false int arg_preset = -1; + int arg_update = 0; } //* A simple argument parser @@ -117,7 +120,7 @@ void argumentParser(const int argc, char **argv) { const string argument = argv[i]; if (is_in(argument, "-h", "--help")) { fmt::println( - "usage: btop [-h] [-v] [-/+t] [-p ] [--utf-force] [--debug]\n\n" + "usage: btop [-h] [-v] [-/+t] [-p ] [-u ] [--utf-force] [--debug]\n\n" "optional arguments:\n" " -h, --help show this help message and exit\n" " -v, --version show version info and exit\n" @@ -125,6 +128,7 @@ void argumentParser(const int argc, char **argv) { " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" " +t, --tty_off force (OFF) tty mode\n" " -p, --preset start with preset, integer value between 0-9\n" + " -u, --update set the program update rate in milliseconds\n" " --utf-force force start even if no UTF-8 locale was detected\n" " --debug start in DEBUG mode: shows microsecond timer for information collect\n" " and screen draw functions and sets loglevel to DEBUG" @@ -159,6 +163,19 @@ void argumentParser(const int argc, char **argv) { exit(1); } } + else if (is_in(argument, "-u", "--update")) { + if (++i >= argc) { + fmt::println("ERROR: Update option needs an argument"); + exit(1); + } + const std::string value = argv[i]; + if (isint(value)) { + Global::arg_update = std::clamp(std::stoi(value), 100, Config::ONE_DAY_MILLIS); + } else { + fmt::println("ERROR: Invalid update rate"); + exit(1); + } + } else if (argument == "--utf-force") Global::utf_force = true; else if (argument == "--debug") @@ -1054,6 +1071,9 @@ int main(int argc, char **argv) { //? ------------------------------------------------ MAIN LOOP ---------------------------------------------------- + if (Global::arg_update != 0) { + Config::set("update_ms", Global::arg_update); + } uint64_t update_ms = Config::getI("update_ms"); auto future_time = time_ms(); diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 535d8c8..b8a5f67 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -488,8 +488,8 @@ namespace Config { if (name == "update_ms" and i_value < 100) validError = "Config value update_ms set too low (<100)."; - else if (name == "update_ms" and i_value > 86400000) - validError = "Config value update_ms set too high (>86400000)."; + else if (name == "update_ms" and i_value > ONE_DAY_MILLIS) + validError = fmt::format("Config value update_ms set too high (>{}).", ONE_DAY_MILLIS); else return true; diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 8d1d579..3651566 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -58,6 +58,8 @@ namespace Config { extern vector available_batteries; extern int current_preset; + constexpr int ONE_DAY_MILLIS = 1000 * 60 * 60 * 24; + [[nodiscard]] std::optional get_config_dir() noexcept; //* Check if string only contains space separated valid names for boxes From 057f4594fbcc7d5540db67fb2ff85a36e40cb03c Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 7 Jan 2024 14:10:40 +0100 Subject: [PATCH 45/91] Fixed config variable show_gpu_info working correctly --- src/btop_draw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 0ceaa2f..eecec45 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -2010,7 +2010,7 @@ namespace Draw { #ifdef GPU_SUPPORT const bool show_gpu_on = Config::getS("show_gpu_info") == "On"; const bool gpus_shown_in_cpu_panel = Gpu::gpu_names.size() > 0 and ( - show_gpu_on or (Config::getS("cpu_graph_lower") == "Auto" and Gpu::shown == 0) + show_gpu_on or (Config::getS("show_gpu_info") == "Auto" and Gpu::shown == 0) ); const int gpus_height_offset = (Gpu::gpu_names.size() - Gpu::shown)*gpus_shown_in_cpu_panel; int gpus_extra_height = gpus_shown_in_cpu_panel ? Gpu::gpu_names.size() - (show_gpu_on ? 0 : Gpu::shown) : 0; From 62964693b30573cc6495cb181ab3adb89d077c20 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 7 Jan 2024 14:51:03 +0100 Subject: [PATCH 46/91] Fixed gpu boxes visibility state not saved and removed unnecessary comments --- src/btop.cpp | 26 ++++++++++++------- src/btop_config.cpp | 5 ++-- src/btop_draw.cpp | 4 +-- src/btop_draw.hpp | 4 +-- src/btop_menu.cpp | 52 ++++++++++++++++++------------------- src/btop_menu.hpp | 12 ++++----- src/btop_shared.hpp | 53 +++++++++++++++++++------------------- src/btop_tools.cpp | 6 ++--- src/btop_tools.hpp | 2 +- src/linux/btop_collect.cpp | 50 +++++++++++++++++------------------ 10 files changed, 110 insertions(+), 104 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index 9ad5012..736854f 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -96,9 +96,9 @@ namespace Global { string exit_error_msg; atomic thread_exception (false); - bool debuginit{}; // defaults to false - bool debug{}; // defaults to false - bool utf_force{}; // defaults to false + bool debuginit{}; + bool debug{}; + bool utf_force{}; uint64_t start_time; @@ -107,9 +107,10 @@ namespace Global { atomic should_quit (false); atomic should_sleep (false); atomic _runner_started (false); + atomic init_conf (false); - bool arg_tty{}; // defaults to false - bool arg_low_color{}; // defaults to false + bool arg_tty{}; + bool arg_low_color{}; int arg_preset = -1; int arg_update = 0; } @@ -418,7 +419,7 @@ namespace Runner { string output; string empty_bg; - bool pause_output{}; // defaults to false + bool pause_output{}; sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; @@ -894,10 +895,10 @@ int main(int argc, char **argv) { } //? Config init - { vector load_warnings; + { + atomic_lock lck(Global::init_conf); + vector load_warnings; Config::load(Config::conf_file, load_warnings); - - if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes")); Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor"))); if (Global::debug) { @@ -918,7 +919,7 @@ int main(int argc, char **argv) { } else { string found; - bool set_failure{}; // defaults to false + bool set_failure{}; for (const auto loc_env : array{"LANG", "LC_ALL"}) { 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); @@ -1018,6 +1019,11 @@ int main(int argc, char **argv) { clean_quit(1); } + if (not Config::check_boxes(Config::getS("shown_boxes"))) { + Config::check_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(); diff --git a/src/btop_config.cpp b/src/btop_config.cpp index b8a5f67..18e60ae 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -507,7 +507,7 @@ namespace Config { else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) validError = fmt::format("Invalid graph symbol identifier for {}: {}", name, value); - else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) + else if (name == "shown_boxes" and not Global::init_conf and not value.empty() and not check_boxes(value)) validError = "Invalid box name(s) in shown_boxes!"; #ifdef GPU_SUPPORT @@ -619,8 +619,7 @@ namespace Config { if (not v_contains(valid_boxes, box)) return false; #ifdef GPU_SUPPORT if (box.starts_with("gpu")) { - size_t gpu_num = stoi(box.substr(3)); - if (gpu_num == 0) gpu_num = 5; + size_t gpu_num = stoi(box.substr(3)) + 1; if (std::cmp_greater(gpu_num, Gpu::gpu_names.size())) return false; } #endif diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index eecec45..acab14c 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -307,8 +307,8 @@ namespace Draw { {"/uptime", ""} }; - static time_t c_time{}; // defaults to 0 - static size_t clock_len{}; // defaults to 0 + static time_t c_time{}; + static size_t clock_len{}; static string clock_str; if (auto n_time = time(nullptr); not force and n_time == c_time) diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index b049a57..205fa85 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -65,8 +65,8 @@ namespace Draw { //* An editable text field class TextEdit { - size_t pos{}; // defaults to 0 - size_t upos{}; // defaults to 0 + size_t pos{}; + size_t upos{}; bool numeric; public: string text; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index bc97dc2..fb119aa 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -49,8 +49,8 @@ namespace Menu { bool redraw{true}; int currentMenu = -1; msgBox messageBox; - int signalToSend{}; // defaults to 0 - int signalKillRet{}; // defaults to 0 + int signalToSend{}; + int signalKillRet{}; const array P_Signals = { "0", @@ -868,8 +868,8 @@ namespace Menu { int signalChoose(const string& key) { auto s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid")); - static int x{}; // defaults to 0 - static int y{}; // defaults to 0 + static int x{}; + static int y{}; static int selected_signal = -1; if (bg.empty()) selected_signal = -1; @@ -1070,8 +1070,8 @@ namespace Menu { int mainMenu(const string& key) { enum MenuItems { Options, Help, Quit }; - static int y{}; // defaults to 0 - static int selected{}; // defaults to 0 + static int y{}; + static int selected{}; static vector colors_selected; static vector colors_normal; auto tty_mode = Config::getB("tty_mode"); @@ -1149,18 +1149,18 @@ namespace Menu { int optionsMenu(const string& key) { enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable}; - static int y{}; // defaults to 0 - static int x{}; // defaults to 0 - static int height{}; // defaults to 0 - static int page{}; // defaults to 0 - static int pages{}; // defaults to 0 - static int selected{}; // defaults to 0 - static int select_max{}; // defaults to 0 - static int item_height{}; // defaults to 0 - static int selected_cat{}; // defaults to 0 - static int max_items{}; // defaults to 0 - static int last_sel{}; // defaults to 0 - static bool editing{}; // defaults to false + static int y{}; + static int x{}; + static int height{}; + static int page{}; + static int pages{}; + static int selected{}; + static int select_max{}; + static int item_height{}; + static int selected_cat{}; + static int max_items{}; + static int last_sel{}; + static bool editing{}; static Draw::TextEdit editor; static string warnings; static bitset<8> selPred; @@ -1196,9 +1196,9 @@ namespace Menu { Theme::updateThemes(); } int retval = Changed; - bool recollect{}; // defaults to false - bool screen_redraw{}; // defaults to false - bool theme_refresh{}; // defaults to false + bool recollect{}; + bool screen_redraw{}; + bool theme_refresh{}; //? Draw background if needed else process input if (redraw) { @@ -1500,11 +1500,11 @@ namespace Menu { } int helpMenu(const string& key) { - static int y{}; // defaults to 0 - static int x{}; // defaults to 0 - static int height{}; // defaults to 0 - static int page{}; // defaults to 0 - static int pages{}; // defaults to 0 + static int y{}; + static int x{}; + static int height{}; + static int page{}; + static int pages{}; if (bg.empty()) page = 0; int retval = Changed; diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 74b30d1..63974e4 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -46,12 +46,12 @@ namespace Menu { //? Strings in content vector is not checked for box width overflow class msgBox { string box_contents, button_left, button_right; - int height{}; // defaults to 0 - int width{}; // defaults to 0 - int boxtype{}; // defaults to 0 - int selected{}; // defaults to 0 - int x{}; // defaults to 0 - int y{}; // defaults to 0 + int height{}; + int width{}; + int boxtype{}; + int selected{}; + int x{}; + int y{}; public: enum BoxTypes { OK, YES_NO, NO_YES }; enum msgReturn { diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index ebd1073..69566c6 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -54,6 +54,7 @@ namespace Global { extern string overlay; extern string clock; extern uid_t real_uid, set_uid; + extern atomic init_conf; } namespace Runner { @@ -228,11 +229,11 @@ namespace Mem { string name; string fstype{}; // defaults to "" std::filesystem::path stat{}; // defaults to "" - int64_t total{}; // defaults to 0 - int64_t used{}; // defaults to 0 - int64_t free{}; // defaults to 0 - int used_percent{}; // defaults to 0 - int free_percent{}; // defaults to 0 + int64_t total{}; + int64_t used{}; + int64_t free{}; + int used_percent{}; + int free_percent{}; array old_io = {0, 0, 0}; deque io_read = {}; @@ -272,12 +273,12 @@ namespace Net { extern std::unordered_map graph_max; struct net_stat { - uint64_t speed{}; // defaults to 0 - uint64_t top{}; // defaults to 0 - uint64_t total{}; // defaults to 0 - uint64_t last{}; // defaults to 0 - uint64_t offset{}; // defaults to 0 - uint64_t rollover{}; // defaults to 0 + uint64_t speed{}; + uint64_t top{}; + uint64_t total{}; + uint64_t last{}; + uint64_t offset{}; + uint64_t rollover{}; }; struct net_info { @@ -285,7 +286,7 @@ namespace Net { std::unordered_map stat = { {"download", {}}, {"upload", {}} }; string ipv4{}; // defaults to "" string ipv6{}; // defaults to "" - bool connected{}; // defaults to false + bool connected{}; }; extern std::unordered_map current_net; @@ -337,32 +338,32 @@ namespace Proc { //* Container for process information struct proc_info { - size_t pid{}; // defaults to 0 + size_t pid{}; string name{}; // defaults to "" string cmd{}; // defaults to "" string short_cmd{}; // defaults to "" - size_t threads{}; // defaults to 0 - int name_offset{}; // defaults to 0 + size_t threads{}; + int name_offset{}; string user{}; // defaults to "" - uint64_t mem{}; // defaults to 0 + uint64_t mem{}; double cpu_p{}; // defaults to = 0.0 double cpu_c{}; // defaults to = 0.0 char state = '0'; - int64_t p_nice{}; // defaults to 0 - uint64_t ppid{}; // defaults to 0 - uint64_t cpu_s{}; // defaults to 0 - uint64_t cpu_t{}; // defaults to 0 + int64_t p_nice{}; + uint64_t ppid{}; + uint64_t cpu_s{}; + uint64_t cpu_t{}; string prefix{}; // defaults to "" - size_t depth{}; // defaults to 0 - size_t tree_index{}; // defaults to 0 - bool collapsed{}; // defaults to false - bool filtered{}; // defaults to false + size_t depth{}; + size_t tree_index{}; + bool collapsed{}; + bool filtered{}; }; //* Container for process info box struct detail_container { - size_t last_pid{}; // defaults to 0 - bool skip_smaps{}; // defaults to false + size_t last_pid{}; + bool skip_smaps{}; proc_info entry; string elapsed, parent, status, io_read, io_write, memory; long long first_mem = -1; diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 47c53bc..8a85ed4 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -54,9 +54,9 @@ namespace rng = std::ranges; //* Collection of escape codes and functions for terminal manipulation namespace Term { - atomic initialized{}; // defaults to false - atomic width{}; // defaults to 0 - atomic height{}; // defaults to 0 + atomic initialized{}; + atomic width{}; + atomic height{}; string current_tty; namespace { diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index bda89f2..705a5eb 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -406,7 +406,7 @@ namespace Tools { //* Sets atomic to true on construct, sets to false on destruct class atomic_lock { atomic& atom; - bool not_true{}; // defaults to false + bool not_true{}; public: atomic_lock(atomic& atom, bool wait = false); ~atomic_lock(); diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 677c3fe..0c1ab66 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -77,8 +77,8 @@ namespace Cpu { vector available_sensors = {"Auto"}; cpu_info current_cpu; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; - bool got_sensors{}; // defaults to false - bool cpu_temp_only{}; // defaults to false + bool got_sensors{}; + bool cpu_temp_only{}; //* Populate found_sensors map bool get_sensors(); @@ -92,9 +92,9 @@ namespace Cpu { struct Sensor { fs::path path; string label; - int64_t temp{}; // defaults to 0 - int64_t high{}; // defaults to 0 - int64_t crit{}; // defaults to 0 + int64_t temp{}; + int64_t high{}; + int64_t crit{}; }; std::unordered_map found_sensors; @@ -542,14 +542,14 @@ namespace Cpu { } string get_cpuHz() { - static int failed{}; // defaults to 0 + static int failed{}; if (failed > 4) return ""s; string cpuhz; try { - double hz{}; // defaults to 0.0 + double hz{}; //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) if (not freq_path.empty()) { hz = stod(readfile(freq_path, "0.0")) / 1000; @@ -605,9 +605,9 @@ namespace Cpu { //? Try to get core mapping from /proc/cpuinfo ifstream cpuinfo(Shared::procPath / "cpuinfo"); if (cpuinfo.good()) { - int cpu{}; // defaults to 0 - int core{}; // defaults to 0 - int n{}; // defaults to 0 + int cpu{}; + int core{}; + int n{}; for (string instr; cpuinfo >> instr;) { if (instr == "processor") { cpuinfo.ignore(SSmax, ':'); @@ -1508,10 +1508,10 @@ namespace Gpu { #endif namespace Mem { - bool has_swap{}; // defaults to false + bool has_swap{}; vector fstab; fs::file_time_type fstab_time; - int disk_ios{}; // defaults to 0 + int disk_ios{}; vector last_found; //?* Find the filepath to the specified ZFS object's stat file @@ -1999,10 +1999,10 @@ namespace Mem { int64_t bytes_read; int64_t bytes_write; int64_t io_ticks; - int64_t bytes_read_total{}; // defaults to 0 - int64_t bytes_write_total{}; // defaults to 0 - int64_t io_ticks_total{}; // defaults to 0 - int64_t objects_read{}; // defaults to 0 + int64_t bytes_read_total{}; + int64_t bytes_write_total{}; + int64_t io_ticks_total{}; + int64_t objects_read{}; // looking through all files that start with 'objset' for (const auto& file: fs::directory_iterator(disk.stat)) { @@ -2078,11 +2078,11 @@ namespace Net { net_info empty_net = {}; vector interfaces; string selected_iface; - int errors{}; // defaults to 0 + int errors{}; std::unordered_map graph_max = { {"download", {}}, {"upload", {}} }; std::unordered_map> max_count = { {"download", {}}, {"upload", {}} }; bool rescale{true}; - uint64_t timestamp{}; // defaults to 0 + uint64_t timestamp{}; //* RAII wrapper for getifaddrs class getifaddr_wrapper { @@ -2171,7 +2171,7 @@ namespace Net { auto& saved_stat = net.at(iface).stat.at(dir); auto& bandwidth = net.at(iface).bandwidth.at(dir); - uint64_t val{}; // defaults to 0 + uint64_t val{}; try { val = (uint64_t)stoull(readfile(sys_file, "0")); } catch (const std::invalid_argument&) {} catch (const std::out_of_range&) {} @@ -2291,15 +2291,15 @@ namespace Proc { std::unordered_map uid_user; string current_sort; string current_filter; - bool current_rev{}; // defaults to false + bool current_rev{}; fs::file_time_type passwd_time; uint64_t cputimes; int collapse = -1, expand = -1; - uint64_t old_cputimes{}; // defaults to 0 - atomic numpids{}; // defaults to 0 - int filter_found{}; // defaults to 0 + uint64_t old_cputimes{}; + atomic numpids{}; + int filter_found{}; detail_container detailed; constexpr size_t KTHREADD = 2; @@ -2431,7 +2431,7 @@ namespace Proc { const int cmult = (per_core) ? Shared::coreCount : 1; bool got_detailed = false; - static size_t proc_clear_count{}; // defaults to 0 + static size_t proc_clear_count{}; //* Use pids from last update if only changing filter, sorting or tree options if (no_update and not current_procs.empty()) { @@ -2506,7 +2506,7 @@ namespace Proc { //? Check if pid already exists in current_procs auto find_old = rng::find(current_procs, pid, &proc_info::pid); - bool no_cache{}; // defaults to false + bool no_cache{}; if (find_old == current_procs.end()) { current_procs.push_back({pid}); find_old = current_procs.end() - 1; From 36842a3bccfb78f5c27642435bff387b42f7d13b Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 7 Jan 2024 15:23:01 +0100 Subject: [PATCH 47/91] v1.3.0 GPU Support Linux and platform support OpenBSD --- CHANGELOG.md | 14 +++++++++++++- README.md | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf273b..00273c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## v1.3.0 -* Added Gpu Support | @romner-set | PR #529 +* Added Gpu Support Linux | @romner-set | PR #529 + +* Added platform support for OpenBSD | @joske | PR #607 * Enable macos clang | @muneebmahmed | PR #666 @@ -42,6 +44,16 @@ * Create adwaita.theme | @flipflop133 | #485 +* Try get terminal size of "/dev/tty" if stdout fails | @imwints | PR #627 + +* Refresh rate program argument | @imwints | PR #640 + +* Improved error handling when determining the config directory | @imwints | #652 + +* Use native POSIX polling syscalls to read input | @lvxnull | #624 + +* Conditional compile on Big Sur and up | @joske | PR #690 + + Various fixes by @imwints, @simplepad, @joske, @gwena, @cpalv, @iambeingtracked, @mattico, @NexAdn ## v1.2.13 diff --git a/README.md b/README.md index fa67b70..0b351a2 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,15 @@ ## News +##### 7 January 2024 + +Btop release v1.3.0 + +Big release with GPU support added for Linux and platform support for OpenBSD. Big thanks to @romner-set (GPU support) and @joske (OpenBSD support) for contributions. +And a multitude of bugfixes and small changes, see [CHANGES.md](CHANGES.md) and latest [release](https://github.com/aristocratos/btop/releases/latest) for detailed list and attributions. + +See news entry below for more information regarding GPU support. + ##### 25 November 2023 GPU monitoring added for Linux! @@ -69,6 +78,9 @@ Big update with version bump to 1.3 coming soon. First release of btop4win available at https://github.com/aristocratos/btop4win +
+More... + ##### 16 January 2022 Release v1.2.0 with FreeBSD support. No release binaries for FreeBSD provided as of yet. @@ -84,9 +96,6 @@ macOS binaries + installer are included for both x86 and ARM64 (Apple Silicon) i Big thank you to [@joske](https://github.com/joske) who wrote the vast majority of the implementation! -
-More... - ##### 30 October 2021 Work on the OSX [macOS] and FreeBSD branches, both initiated and mostly worked on by [@joske](https://github.com/joske), will likely be completed in the coming weeks. From 4ab43fd159ab03d80707ea71287a73931eeedd55 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Sun, 7 Jan 2024 18:59:21 +0100 Subject: [PATCH 48/91] Bump cmake to v1.3.0 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f8c546..c82b462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") endif() project("btop" - VERSION 1.2.13 + VERSION 1.3.0 DESCRIPTION "A monitor of resources" HOMEPAGE_URL "https://github.com/aristocratos/btop" LANGUAGES CXX @@ -97,7 +97,7 @@ if(BTOP_WERROR) endif() if(NOT APPLE) - target_compile_options(btop PRIVATE -fstack-clash-protection) + target_compile_options(btop PRIVATE -fstack-clash-protection) endif() check_cxx_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR) if(HAS_FSTACK_PROTECTOR) From 19c6099c7c48efd85959bed7391a19449091f9bb Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 8 Jan 2024 13:19:16 +0100 Subject: [PATCH 49/91] Use _FORTIFY_SOURCE=3 and put it behind a build flag --- CMakeLists.txt | 3 ++- Makefile | 7 ++++++- README.md | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f8c546..2480efb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(BTOP_LTO "Enable LTO" ON) option(BTOP_USE_MOLD "Use mold to link btop" OFF) option(BTOP_PEDANTIC "Enable a bunch of additional warnings" OFF) option(BTOP_WERROR "Compile with warnings as errors" OFF) +option(BTOP_FORTIFY "Detect buffer overflows with _FORTIFY_SOURCE=3" ON) option(BTOP_GPU "Enable GPU support" ON) cmake_dependent_option(BTOP_RSMI_STATIC "Link statically to ROCm SMI" OFF "BTOP_GPU" OFF) @@ -112,7 +113,7 @@ target_compile_definitions(btop PRIVATE _FILE_OFFSET_BITS=64 $<$:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> # Only has an effect with optimizations enabled - $<$>:_FORTIFY_SOURCE=2> + $<$>,$>:_FORTIFY_SOURCE=3> ) target_include_directories(btop SYSTEM PRIVATE include) diff --git a/Makefile b/Makefile index 36e2ea1..36153a3 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,11 @@ ifeq ($(GPU_SUPPORT),true) override ADDFLAGS += -DGPU_SUPPORT endif +FORTIFY_SOURCE ?= true +ifeq ($(FORTIFY_SOURCE),true) + override ADDFLAGS += -D_FORTIFY_SOURCE=3 +endif + #? Compiler and Linker ifeq ($(shell $(CXX) --version | grep clang >/dev/null 2>&1; echo $$?),0) override CXX_IS_CLANG := true @@ -174,7 +179,7 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main override REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -pedantic OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO) -LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) +LDCXXFLAGS := -pthread -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) diff --git a/README.md b/README.md index 0b351a2..827f3a3 100644 --- a/README.md +++ b/README.md @@ -405,6 +405,7 @@ Also needs a UTF8 locale and a font that covers: | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `GPU_SUPPORT=` | Enable/disable GPU support (Enabled by default on X86_64 Linux) | | `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU | | `ADDFLAGS=` | For appending flags to both compiler and linker | @@ -503,6 +504,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DBTOP_GPU=` | Enable GPU support (ON by default) | | `-DBTOP_RSMI_STATIC=` | Build and link the ROCm SMI library statically (OFF by default) | | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | @@ -576,6 +578,7 @@ Also needs a UTF8 locale and a font that covers: | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | | `CXX=` | Manualy set which compiler to use | @@ -674,6 +677,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | To force any specific compiler, run `CXX= cmake -B build -G Ninja` @@ -742,6 +746,7 @@ Also needs a UTF8 locale and a font that covers: | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | | `CXX=` | Manualy set which compiler to use | @@ -851,6 +856,7 @@ Also needs a UTF8 locale and a font that covers: | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | _**Note:** Static linking does not work with GCC._ @@ -921,6 +927,7 @@ Also needs a UTF8 locale and a font that covers: | `STRIP=true` | To force stripping of debug symbols (adds `-s` linker flag) | | `DEBUG=true` | Sets OPTFLAGS to `-O0 -g` and enables more verbose debug logging | | `ARCH=` | To manually set the target architecture | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | | `CXX=` | Manualy set which compiler to use | From 3cbb0484a44fd3c829589b92c604a16e12465434 Mon Sep 17 00:00:00 2001 From: v4u6h4n Date: Thu, 11 Jan 2024 12:19:09 +1100 Subject: [PATCH 50/91] Update README.md Added optional dependencies to 'Prerequisites' section. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 0b351a2..a886524 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,18 @@ Also needs a UTF8 locale and a font that covers: * Unicode Block “Geometric Shapes” U+25A0 - U+25FF * Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F +### **Optional Dependencies (Needed for GPU monitoring)** + + * **NVIDIA** + +If you have an NVIDIA GPU you must use an official NVIDIA driver, both the closed-source and open-source ones have been verified to work. + +In addition to that you must also have the nvidia-ml dynamic library installed, which should be included with the driver package of your distribution. + + * **AMD** + +If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packaged for your distribution. + ### **Notice (Text rendering issues)** * If you are having problems with the characters in the graphs not looking like they do in the screenshots, it's likely a problem with your systems configured fallback font not having support for braille characters. From ca368c5b89e591be6b3795efaf6aa85b459ed3db Mon Sep 17 00:00:00 2001 From: aristocratos Date: Thu, 11 Jan 2024 09:35:20 +0100 Subject: [PATCH 51/91] Added link to compile section --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a886524..d237d42 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,10 @@ Also needs a UTF8 locale and a font that covers: ### **Optional Dependencies (Needed for GPU monitoring)** +GPU monitoring also requires a btop binary built with GPU support (`GPU_SUPPORT=true` flag). + +See [GPU compatibility](#gpu-compatibility) section for more about compiling with GPU support. + * **NVIDIA** If you have an NVIDIA GPU you must use an official NVIDIA driver, both the closed-source and open-source ones have been verified to work. From e936339038dc77ded0b51ab3f3b1341808a85e4e Mon Sep 17 00:00:00 2001 From: aristocratos Date: Thu, 11 Jan 2024 10:39:39 +0100 Subject: [PATCH 52/91] Undefine FORTIFY_SOURCE before setting new value when enabled --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36153a3..ed80899 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ endif FORTIFY_SOURCE ?= true ifeq ($(FORTIFY_SOURCE),true) - override ADDFLAGS += -D_FORTIFY_SOURCE=3 + override ADDFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 endif #? Compiler and Linker From b2df50396b0405b8ce2cfa3b10d4542cfd9d73cd Mon Sep 17 00:00:00 2001 From: "Jakob P. Liljenberg" Date: Thu, 11 Jan 2024 19:27:29 +0100 Subject: [PATCH 53/91] Update README.md -> Fixed bad links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9191071..13e57ae 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Btop release v1.3.0 -Big release with GPU support added for Linux and platform support for OpenBSD. Big thanks to @romner-set (GPU support) and @joske (OpenBSD support) for contributions. -And a multitude of bugfixes and small changes, see [CHANGES.md](CHANGES.md) and latest [release](https://github.com/aristocratos/btop/releases/latest) for detailed list and attributions. +Big release with GPU support added for Linux and platform support for OpenBSD. Big thanks to [@romner-set](https://github.com/romner-set) (GPU support) and [@joske](https://github.com/joske) (OpenBSD support) for contributions. +And a multitude of bugfixes and small changes, see [CHANGELOG.md](CHANGELOG.md) and latest [release](https://github.com/aristocratos/btop/releases/latest) for detailed list and attributions. See news entry below for more information regarding GPU support. From 6a6f514f8097ba65186f30c5ecd7a7a3bbcc8f2e Mon Sep 17 00:00:00 2001 From: "Helmut K. C. Tessarek" Date: Fri, 12 Jan 2024 11:28:42 -0500 Subject: [PATCH 54/91] fix: increase interface name length to 15 --- src/btop_draw.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index acab14c..5e3cc5b 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -1361,6 +1361,7 @@ namespace Net { int x = 1, y, width = 20, height; int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; + const int MAX_IFNAMSIZ = 15; string old_ip; std::unordered_map graphs; string box; @@ -1381,7 +1382,7 @@ namespace Net { out.reserve(width * height); const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; - const int i_size = min((int)selected_iface.size(), 10); + const int i_size = min((int)selected_iface.size(), MAX_IFNAMSIZ); const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); @@ -1403,7 +1404,7 @@ namespace Net { //? Interface selector and buttons out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + "" + title_right + + uresize(selected_iface, MAX_IFNAMSIZ) + Theme::c("hi_fg") + " n>" + title_right + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; From 05da55c54995147beb70bd590fd1e7bf29b329e7 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 15 Jan 2024 15:55:04 +0100 Subject: [PATCH 55/91] Fix abort in locale detection on OpenBSD For whatever reason catch doesn't work for exceptions thrown in a dynamically linked library and the program aborts. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ed80899..30ce82d 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ else ifeq ($(PLATFORM_LC),macos) else ifeq ($(PLATFORM_LC),openbsd) PLATFORM_DIR := openbsd THREADS := $(shell sysctl -n hw.ncpu || echo 1) - override ADDFLAGS += -lkvm + override ADDFLAGS += -lkvm -static-libstdc++ export MAKE = gmake SU_GROUP := wheel else From cec251bf0569bdbf92ff2abfc85661ad030148f3 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 15 Jan 2024 16:00:34 +0100 Subject: [PATCH 56/91] Allow the Findkvm module on all BSDs --- cmake/Modules/Findkvm.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/Findkvm.cmake b/cmake/Modules/Findkvm.cmake index a0847de..9e4d82b 100644 --- a/cmake/Modules/Findkvm.cmake +++ b/cmake/Modules/Findkvm.cmake @@ -3,7 +3,7 @@ # Find libkvm, the Kernel Data Access Library # -if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") +if(BSD) find_path(kvm_INCLUDE_DIR NAMES kvm.h) find_library(kvm_LIBRARY NAMES kvm) From 57752df6fc9691e359f549c6eb85673095da292c Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 15 Jan 2024 16:00:54 +0100 Subject: [PATCH 57/91] CMake: Enable OpenBSD --- CMakeLists.txt | 8 ++++++ README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd39946..aec6782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,8 @@ if(APPLE) target_sources(btop PRIVATE src/osx/btop_collect.cpp src/osx/sensors.cpp src/osx/smc.cpp) elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") target_sources(btop PRIVATE src/freebsd/btop_collect.cpp) +elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + target_sources(btop PRIVATE src/openbsd/btop_collect.cpp src/openbsd/sysctlbyname.cpp) elseif(LINUX) target_sources(btop PRIVATE src/linux/btop_collect.cpp) else() @@ -185,6 +187,12 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") find_package(kvm REQUIRED) target_link_libraries(btop elf::elf kvm::kvm) endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(btop PRIVATE -static-libstdc++) + endif() + find_package(kvm REQUIRED) + target_link_libraries(btop kvm::kvm) endif() install(TARGETS btop RUNTIME) diff --git a/README.md b/README.md index 9191071..e555784 100644 --- a/README.md +++ b/README.md @@ -995,6 +995,74 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa gmake help ``` +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires GCC, CMake, Ninja and Git + + _**Note:** LLVM's libc++ shipped with OpenBSD 7.4 is too old and cannot compile btop._ + + ```bash + pkg_add cmake g++%11 git ninja + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + ```bash + # Configure + CXX=eg++ cmake -B build -G Ninja + # Build + cmake --build build + ``` + + This will automatically build a release version of btop. + + Some useful options to pass to the configure step: + + | Configure flag | Description | + |---------------------------------|-------------------------------------------------------------------------| + | `-DBTOP_LTO=` | Enables link time optimization (ON by default) | + | `-DBTOP_USE_MOLD=` | Use mold to link btop (OFF by default) | + | `-DBTOP_PEDANTIC=` | Compile with additional warnings (OFF by default) | + | `-DBTOP_WERROR=` | Compile with warnings as errors (OFF by default) | + | `-DBTOP_FORTIFY=` | Detect buffer overflows with `_FORTIFY_SOURCE=3` (ON by default) | + | `-DCMAKE_INSTALL_PREFIX=` | The installation prefix ('/usr/local' by default) | + + To force any other compiler, run `CXX= cmake -B build -G Ninja` + +4. **Install** + + ```bash + cmake --install build + ``` + + May require root privileges + +5. **Uninstall** + + CMake doesn't generate an uninstall target by default. To remove installed files, run + ``` + cat build/install_manifest.txt | xargs rm -irv + ``` + +6. **Cleanup build directory** + + ```bash + cmake --build build -t clean + ``` +
## Installing the snap From a44ce1c3a1036c376d105ee7aba81ba7e60c87ae Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 15 Jan 2024 19:23:47 +0100 Subject: [PATCH 58/91] Make BTOP_DEBUG also work for CMake --- src/btop_tools.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 705a5eb..766cc95 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -18,6 +18,10 @@ tab-size = 4 #pragma once +#if !defined(NDEBUG) +# define BTOP_DEBUG +#endif + #include // for std::ranges::count_if #include #include From 67a674e8507c25ac77737cbab3ea7b9731902171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Oliver=20Pito=C5=88=C3=A1k?= Date: Tue, 16 Jan 2024 19:02:20 +0100 Subject: [PATCH 59/91] btop now reacts to SIGUSR1 by reloading config --- src/btop.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/btop.cpp b/src/btop.cpp index 736854f..33c1255 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -362,7 +362,12 @@ void _signal_handler(const int sig) { term_resize(); break; case SIGUSR1: - // Input::poll interrupt + vector warnings; + Config::load(Config::conf_file, warnings); + Theme::setTheme(); + Draw::banner_gen(0, 0, false, true); + Draw::calcSizes(); + Runner::run("all", false, true); break; } } From 3174c83b43739995d119aa372d6a5f6d45b27b9e Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 15 Jan 2024 19:17:05 +0100 Subject: [PATCH 60/91] -DFMT_HEADER_ONLY as a compiler flag This just defines FMT_HEADER_ONLY everywhere instead of just in all files that include `btop_tools.hpp`, in case the statement gets removed there. --- CMakeLists.txt | 1 + Makefile | 2 +- src/btop.cpp | 1 + src/btop_tools.hpp | 4 +--- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd39946..df8d347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ if(HAS_FCF_PROTECTION) endif() target_compile_definitions(btop PRIVATE + FMT_HEADER_ONLY _FILE_OFFSET_BITS=64 $<$:_GLIBCXX_ASSERTIONS _LIBCPP_ENABLE_ASSERTIONS=1> # Only has an effect with optimizations enabled diff --git a/Makefile b/Makefile index ed80899..3aae5d6 100644 --- a/Makefile +++ b/Makefile @@ -179,7 +179,7 @@ override GOODFLAGS := $(foreach flag,$(TESTFLAGS),$(strip $(shell echo "int main override REQFLAGS := -std=c++20 WARNFLAGS := -Wall -Wextra -pedantic OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO) -LDCXXFLAGS := -pthread -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) +LDCXXFLAGS := -pthread -DFMT_HEADER_ONLY -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) diff --git a/src/btop.cpp b/src/btop.cpp index 736854f..aac8e9c 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -53,6 +53,7 @@ tab-size = 4 #include "btop_draw.hpp" #include "btop_menu.hpp" #include "fmt/core.h" +#include "fmt/ostream.h" using std::atomic; using std::cout; diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 705a5eb..708fddf 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -42,11 +42,9 @@ tab-size = 4 #define HOST_NAME_MAX 64 #endif #endif -#define FMT_HEADER_ONLY + #include "fmt/core.h" #include "fmt/format.h" -#include "fmt/ostream.h" -#include "fmt/ranges.h" using std::array; using std::atomic; From e047f88bd5261bca1bb3d59c60b8b4368d3573ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Oliver=20Pito=C5=88=C3=A1k?= Date: Wed, 17 Jan 2024 16:41:58 +0100 Subject: [PATCH 61/91] SIGUSR2 signal now reloads the config --- src/btop.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/btop.cpp b/src/btop.cpp index 33c1255..94bf8a9 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -362,6 +362,9 @@ void _signal_handler(const int sig) { term_resize(); break; case SIGUSR1: + // Input::poll interrupt + break; + case SIGUSR2: vector warnings; Config::load(Config::conf_file, warnings); Theme::setTheme(); @@ -1040,6 +1043,7 @@ int main(int argc, char **argv) { std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); std::signal(SIGUSR1, _signal_handler); + std::signal(SIGUSR2, _signal_handler); sigset_t mask; sigemptyset(&mask); From fb994b69ebff8cc5f937ea8b0a587b5a5b0c6c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Oliver=20Pito=C5=88=C3=A1k?= Date: Wed, 17 Jan 2024 17:27:18 +0100 Subject: [PATCH 62/91] added ctrl+r shortcut to reload config from disk --- src/btop_input.cpp | 7 +++++-- src/btop_menu.cpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/btop_input.cpp b/src/btop_input.cpp index fbbae58..6bfe9d8 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -41,6 +41,7 @@ namespace Input { //* Map for translating key codes to readable values const std::unordered_map Key_escapes = { {"\033", "escape"}, + {"\x12", "ctrl_r"}, {"\n", "enter"}, {" ", "space"}, {"\x7f", "backspace"}, @@ -258,8 +259,10 @@ namespace Input { Draw::calcSizes(); Runner::run("all", false, true); return; - } - else + } else if (is_in(key, "ctrl_r")) { + kill(getpid(), SIGUSR2); + return; + } else keep_going = true; if (not keep_going) return; diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index fb119aa..b7f44bc 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -177,6 +177,7 @@ namespace Menu { {"F2, o", "Shows options."}, {"F1, ?, h", "Shows this window."}, {"ctrl + z", "Sleep program and put in background."}, + {"ctrl + r", "Reloads config file from disk."}, {"q, ctrl + c", "Quits program."}, {"+, -", "Add/Subtract 100ms to/from update timer."}, {"Up, Down", "Select in process list."}, From 61105e46b77f81dfcfbc968f8f6eb9b89d4a541e Mon Sep 17 00:00:00 2001 From: jkre Date: Mon, 22 Jan 2024 22:20:33 +0100 Subject: [PATCH 63/91] Add battery power draw to battery inforamtion tuple for openbsd and set it to a constant --- src/openbsd/btop_collect.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index df35662..c0f7b9f 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -385,8 +385,8 @@ namespace Cpu { return core_map; } - auto get_battery() -> tuple { - if (not has_battery) return {0, 0, ""}; + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, 0, ""}; long seconds = -1; uint32_t percent = -1; @@ -417,7 +417,7 @@ namespace Cpu { } } - return {percent, seconds, status}; + return {percent, -1, seconds, status}; } auto collect(bool no_update) -> cpu_info & { From c750543950304d8828e21ab5c6e2976c0c54235d Mon Sep 17 00:00:00 2001 From: jkre Date: Mon, 22 Jan 2024 22:32:16 +0100 Subject: [PATCH 64/91] Fix missing value in battery status tuple for openbsd --- src/openbsd/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index c0f7b9f..3784ea9 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -202,7 +202,7 @@ namespace Cpu { string cpuName; string cpuHz; bool has_battery = true; - tuple current_bat; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle"}; From 771702019743e7ee10da71079ff91edd4106fb41 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Tue, 23 Jan 2024 16:41:26 +0100 Subject: [PATCH 65/91] Include build info in binary --- .gitignore | 2 ++ CMakeLists.txt | 15 +++++++++++++++ Makefile | 21 +++++++++++++++++---- src/btop.cpp | 22 ++++++++++++++++++++-- src/config.h.in | 10 ++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 src/config.h.in diff --git a/.gitignore b/.gitignore index 7e2ed8f..c31e456 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ stage/ build bin btop +/obj/ +config.h .*/ # Optional libraries diff --git a/CMakeLists.txt b/CMakeLists.txt index cd39946..04edbed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,21 @@ if(NOT CXX_HAVE_RANGES) message(FATAL_ERROR "The compiler doesn't support ") endif() +# Generate build info +execute_process( + COMMAND "git" "rev-parse" "--short" "HEAD" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) +set(CONFIGURE_COMMAND + "cmake -DBTOP_STATIC=${BTOP_STATIC} -DBTOP_USE_MOLD=${BTOP_USE_MOLD} -DBTOP_FORTIFY=${BTOP_FORTIFY} -DBTOP_GPU=${BTOP_GPU}" +) +get_filename_component(CXX_COMPILER_BASENAME "${CMAKE_CXX_COMPILER}" NAME) +set(COMPILER "${CXX_COMPILER_BASENAME}") +set(COMPILER_VERSION "${CMAKE_CXX_COMPILER_VERSION}") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY IMMEDIATE) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + # Check for and enable LTO check_ipo_supported(RESULT ipo_supported) if(ipo_supported AND BTOP_LTO) diff --git a/Makefile b/Makefile index ed80899..38ab1be 100644 --- a/Makefile +++ b/Makefile @@ -163,6 +163,12 @@ else LTO := $(THREADS) endif +GIT_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null || true) +CONFIGURE_COMMAND := $(MAKE) STATIC=$(STATIC) FORTIFY_SOURCE=$(FORTIFY_SOURCE) +ifeq ($(PLATFORM_LC),linux) + CONFIGURE_COMMAND += GPU_SUPPORT=$(GPU_SUPPORT) RSMI_STATIC=$(RSMI_STATIC) +endif + #? The Directories, Source, Includes, Objects and Binary SRCDIR := src INCDIRS := include $(wildcard lib/**/include) @@ -182,7 +188,7 @@ OPTFLAGS := -O2 -ftree-vectorize -flto=$(LTO) LDCXXFLAGS := -pthread -D_GLIBCXX_ASSERTIONS -D_FILE_OFFSET_BITS=64 $(GOODFLAGS) $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) -INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) +INC := $(foreach incdir,$(INCDIRS),-isystem $(incdir)) -I$(SRCDIR) -I$(BUILDDIR) SU_USER := root ifdef DEBUG @@ -222,7 +228,7 @@ endif #? Default Make .ONESHELL: -all: | info rocm_smi info-quiet directories btop +all: | info rocm_smi info-quiet directories config.h btop ifneq ($(QUIET),true) info: @@ -267,6 +273,13 @@ directories: @$(VERBOSE) || printf "mkdir -p $(BUILDDIR)/$(PLATFORM_DIR)\n" @mkdir -p $(BUILDDIR)/$(PLATFORM_DIR) +config.h: $(BUILDDIR)/config.h + +$(BUILDDIR)/config.h: $(SRCDIR)/config.h.in | directories + @$(QUIET) || printf "\033[1mConfiguring $(BUILDDIR)/config.h\033[0m\n" + @$(VERBOSE) || printf 'sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null\n' + @sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null + #? Clean only Objects clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @@ -362,7 +375,7 @@ btop: $(OBJECTS) | rocm_smi directories #? Compile .ONESHELL: -$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories config.h @sleep 0.3 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" @@ -371,4 +384,4 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories @printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n" #? Non-File Targets -.PHONY: all msg help pre +.PHONY: all config.h msg help pre diff --git a/src/btop.cpp b/src/btop.cpp index 736854f..caf2ba5 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -52,6 +52,7 @@ tab-size = 4 #include "btop_theme.hpp" #include "btop_draw.hpp" #include "btop_menu.hpp" +#include "config.h" #include "fmt/core.h" using std::atomic; @@ -115,6 +116,19 @@ namespace Global { int arg_update = 0; } +static void print_version() { + if constexpr (GIT_COMMIT.empty()) { + fmt::print("btop version: {}\n", Global::Version); + } else { + fmt::print("btop version: {}+{}\n", Global::Version, GIT_COMMIT); + } +} + +static void print_version_with_build_info() { + print_version(); + fmt::print("Compiled with: {} ({})\nConfigured with: {}\n", COMPILER, COMPILER_VERSION, CONFIGURE_COMMAND); +} + //* A simple argument parser void argumentParser(const int argc, char **argv) { for(int i = 1; i < argc; i++) { @@ -136,8 +150,12 @@ void argumentParser(const int argc, char **argv) { ); exit(0); } - else if (is_in(argument, "-v", "--version")) { - fmt::println("btop version: {}", Global::Version); + else if (is_in(argument, "-v")) { + print_version(); + exit(0); + } + else if (is_in(argument, "--version")) { + print_version_with_build_info(); exit(0); } else if (is_in(argument, "-lc", "--low-color")) { diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..f4a6ed3 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +constexpr std::string_view GIT_COMMIT = "@GIT_COMMIT@"; +constexpr std::string_view COMPILER = "@COMPILER@"; +constexpr std::string_view COMPILER_VERSION = "@COMPILER_VERSION@"; +constexpr std::string_view CONFIGURE_COMMAND = "@CONFIGURE_COMMAND@"; From bdc8d0151daa659cd3726feed6884d2cdb5ed68e Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Tue, 23 Jan 2024 17:42:16 +0100 Subject: [PATCH 66/91] Ask for 'btop --version' in issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7ed6a21..e4096cb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -28,10 +28,9 @@ Any bug that can be solved by just reading the [prerequisites](https://github.co [If applicable, add screenshots to help explain your problem.] **Info (please complete the following information):** - - btop++ version: `btop -v` + - btop++ version: `btop --version` - If using snap: `snap info btop` - Binary: [self compiled or static binary from release] - - (If compiled) Compiler and version: - Architecture: [x86_64, aarch64, etc.] `uname -m` - Platform: [Linux, FreeBSD, OsX] - (Linux) Kernel: `uname -r` From 81d09860f7b815a662c5192e36fcf5e83b3729f6 Mon Sep 17 00:00:00 2001 From: rliang <48518695+rkmcode@users.noreply.github.com> Date: Thu, 25 Jan 2024 03:36:15 +0800 Subject: [PATCH 67/91] Fix basic_string::_M_create exception when 1000>hz>999.5, round(hz)=1000 btop_draw.cpp: 793: Symbols::h_line * (7 - cpuHz.size()) exception --- src/linux/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0c1ab66..5e0af36 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -583,7 +583,7 @@ namespace Cpu { cpuhz += " GHz"; } else if (hz > 0) - cpuhz = to_string((int)round(hz)) + " MHz"; + cpuhz = to_string((int)hz) + " MHz"; } catch (const std::exception& e) { From 55111315723d3773ea2a8235c294e33d0a66eaf1 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Fri, 26 Jan 2024 22:11:23 +0100 Subject: [PATCH 68/91] Support for dynamic loading of ROCm v6 --- src/linux/btop_collect.cpp | 71 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0c1ab66..e9097d9 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -174,23 +174,29 @@ namespace Gpu { rsmi_clk_type_t, rsmi_memory_type_t; - struct rsmi_frequencies_t {uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; + struct rsmi_version_t {uint32_t major, minor, patch; const char* build;}; + struct rsmi_frequencies_t_v5 {uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; + struct rsmi_frequencies_t_v6 {bool has_deep_sleep; uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; //? Function pointers rsmi_status_t (*rsmi_init)(uint64_t); rsmi_status_t (*rsmi_shut_down)(); + rsmi_status_t (*rsmi_version_get)(rsmi_version_t*); rsmi_status_t (*rsmi_num_monitor_devices)(uint32_t*); rsmi_status_t (*rsmi_dev_name_get)(uint32_t, char*, size_t); rsmi_status_t (*rsmi_dev_power_cap_get)(uint32_t, uint32_t, uint64_t*); rsmi_status_t (*rsmi_dev_temp_metric_get)(uint32_t, uint32_t, rsmi_temperature_metric_t, int64_t*); rsmi_status_t (*rsmi_dev_busy_percent_get)(uint32_t, uint32_t*); rsmi_status_t (*rsmi_dev_memory_busy_percent_get)(uint32_t, uint32_t*); - rsmi_status_t (*rsmi_dev_gpu_clk_freq_get)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t*); + rsmi_status_t (*rsmi_dev_gpu_clk_freq_get_v5)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t_v5*); + rsmi_status_t (*rsmi_dev_gpu_clk_freq_get_v6)(uint32_t, rsmi_clk_type_t, rsmi_frequencies_t_v6*); rsmi_status_t (*rsmi_dev_power_ave_get)(uint32_t, uint32_t, uint64_t*); rsmi_status_t (*rsmi_dev_memory_total_get)(uint32_t, rsmi_memory_type_t, uint64_t*); rsmi_status_t (*rsmi_dev_memory_usage_get)(uint32_t, rsmi_memory_type_t, uint64_t*); rsmi_status_t (*rsmi_dev_pci_throughput_get)(uint32_t, uint64_t*, uint64_t*, uint64_t*); + uint32_t version_major = 0; + //? Data void* rsmi_dl_handle; #endif @@ -1265,7 +1271,6 @@ namespace Gpu { LOAD_SYM(rsmi_dev_temp_metric_get); LOAD_SYM(rsmi_dev_busy_percent_get); LOAD_SYM(rsmi_dev_memory_busy_percent_get); - LOAD_SYM(rsmi_dev_gpu_clk_freq_get); LOAD_SYM(rsmi_dev_power_ave_get); LOAD_SYM(rsmi_dev_memory_total_get); LOAD_SYM(rsmi_dev_memory_usage_get); @@ -1281,6 +1286,26 @@ namespace Gpu { return false; } + #if !defined(RSMI_STATIC) + //? Check version + rsmi_version_t version; + result = rsmi_version_get(&version); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get version"); + return false; + } else if (version.major == 5) { + if ((rsmi_dev_gpu_clk_freq_get_v5 = (decltype(rsmi_dev_gpu_clk_freq_get_v5))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr) + return false; + } else if (version.major == 6) { + if ((rsmi_dev_gpu_clk_freq_get_v6 = (decltype(rsmi_dev_gpu_clk_freq_get_v6))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr) + return false; + } else { + Logger::warning("ROCm SMI: Dynamic loading only supported for version 5 and 6"); + return false; + } + version_major = version.major; + #endif + //? Device count result = rsmi_num_monitor_devices(&device_count); if (result != RSMI_STATUS_SUCCESS) { @@ -1364,7 +1389,46 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.mem_utilization = false; } else gpus_slice[i].mem_utilization_percent.push_back((long long)utilization); } + #if !defined(RSMI_STATIC) + //? Clock speeds + if (gpus_slice[i].supported_functions.gpu_clock) { + if (version_major == 5) { + rsmi_frequencies_t_v5 frequencies; + result = rsmi_dev_gpu_clk_freq_get_v5(i, RSMI_CLK_TYPE_SYS, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; + } else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + else if (version_major == 6) { + rsmi_frequencies_t_v6 frequencies; + result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_SYS, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get GPU clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; + } else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + } + if (gpus_slice[i].supported_functions.mem_clock) { + if (version_major == 5) { + rsmi_frequencies_t_v5 frequencies; + result = rsmi_dev_gpu_clk_freq_get_v5(i, RSMI_CLK_TYPE_MEM, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; + } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + else if (version_major == 6) { + rsmi_frequencies_t_v6 frequencies; + result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_MEM, &frequencies); + if (result != RSMI_STATUS_SUCCESS) { + Logger::warning("ROCm SMI: Failed to get VRAM clock speed: "); + if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; + } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz + } + } + #else //? Clock speeds if (gpus_slice[i].supported_functions.gpu_clock) { rsmi_frequencies_t frequencies; @@ -1383,6 +1447,7 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } + #endif //? Power usage & state if (gpus_slice[i].supported_functions.pwr_usage) { From 8019e117c759b747601efe73de945e2a49eb19c2 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Fri, 26 Jan 2024 22:45:11 +0100 Subject: [PATCH 69/91] Added continuous build for testing Gpu support --- .github/workflows/continuous-build-gpu.yml | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/continuous-build-gpu.yml diff --git a/.github/workflows/continuous-build-gpu.yml b/.github/workflows/continuous-build-gpu.yml new file mode 100644 index 0000000..2c64738 --- /dev/null +++ b/.github/workflows/continuous-build-gpu.yml @@ -0,0 +1,45 @@ +name: Continuous Build Gpu + +on: + workflow_dispatch: + push: + branches: + - main + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - '!src/openbsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-gpu.yml' + pull_request: + branches: + - main + paths: + - 'src/**' + - '!src/osx/**' + - '!src/freebsd/**' + - '!src/openbsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-gpu.yml' + +jobs: + gpu_build_linux: + runs-on: ubuntu-latest + container: alpine:edge + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: apk add --no-cache --update gcc + + - name: Compile + run: make CXX=g++ GPU_SUPPORT=true + From eab6c5813795ebbe1005bb7c124103023d193571 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Fri, 26 Jan 2024 22:48:04 +0100 Subject: [PATCH 70/91] Fix dependencies for Continuous Build Gpu --- .github/workflows/continuous-build-gpu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-build-gpu.yml b/.github/workflows/continuous-build-gpu.yml index 2c64738..e3bdf01 100644 --- a/.github/workflows/continuous-build-gpu.yml +++ b/.github/workflows/continuous-build-gpu.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 - name: Install build tools - run: apk add --no-cache --update gcc + run: apk add --no-cache --update gcc g++ make - name: Compile run: make CXX=g++ GPU_SUPPORT=true From 4461a431ae7d4608db589b9a1560e43bc6d1e5a2 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Fri, 26 Jan 2024 23:29:31 +0100 Subject: [PATCH 71/91] Fixed incorrect used and available memory for OSX --- src/osx/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 76b63c0..860e457 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -686,7 +686,7 @@ namespace Mem { if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&p, &info_size) == 0) { mem.stats.at("free") = p.free_count * Shared::pageSize; mem.stats.at("cached") = p.external_page_count * Shared::pageSize; - mem.stats.at("used") = (p.active_count + p.inactive_count + p.wire_count) * Shared::pageSize; + mem.stats.at("used") = (p.active_count + p.wire_count) * Shared::pageSize; mem.stats.at("available") = Shared::totalMem - mem.stats.at("used"); } From 7d3617a274438c4dde287ff59dd82085609028ae Mon Sep 17 00:00:00 2001 From: fxzjshm Date: Sat, 27 Jan 2024 15:31:02 +0800 Subject: [PATCH 72/91] Fix dynamic loading of ROCm v6 Corrected definition of `rsmi_frequencies_t` of ROCm v5 and v6. Also added the missing `LOAD_SYM(rsmi_version_get);` as it is used later. Fixes: 5511131 ("Support for dynamic loading of ROCm v6") Signed-off-by: fxzjshm --- src/linux/btop_collect.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index e9097d9..eb8c2b7 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -160,14 +160,15 @@ namespace Gpu { namespace Rsmi { #if !defined(RSMI_STATIC) //? RSMI defines, structs & typedefs - #define RSMI_MAX_NUM_FREQUENCIES 32 - #define RSMI_STATUS_SUCCESS 0 - #define RSMI_MEM_TYPE_VRAM 0 - #define RSMI_TEMP_CURRENT 0 - #define RSMI_TEMP_TYPE_EDGE 0 - #define RSMI_CLK_TYPE_MEM 4 - #define RSMI_CLK_TYPE_SYS 0 - #define RSMI_TEMP_MAX 1 + #define RSMI_MAX_NUM_FREQUENCIES_V5 32 + #define RSMI_MAX_NUM_FREQUENCIES_V6 33 + #define RSMI_STATUS_SUCCESS 0 + #define RSMI_MEM_TYPE_VRAM 0 + #define RSMI_TEMP_CURRENT 0 + #define RSMI_TEMP_TYPE_EDGE 0 + #define RSMI_CLK_TYPE_MEM 4 + #define RSMI_CLK_TYPE_SYS 0 + #define RSMI_TEMP_MAX 1 typedef int rsmi_status_t, rsmi_temperature_metric_t, @@ -175,8 +176,8 @@ namespace Gpu { rsmi_memory_type_t; struct rsmi_version_t {uint32_t major, minor, patch; const char* build;}; - struct rsmi_frequencies_t_v5 {uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; - struct rsmi_frequencies_t_v6 {bool has_deep_sleep; uint32_t num_supported, current, frequency[RSMI_MAX_NUM_FREQUENCIES];}; + struct rsmi_frequencies_t_v5 {uint32_t num_supported, current; uint64_t frequency[RSMI_MAX_NUM_FREQUENCIES_V5];}; + struct rsmi_frequencies_t_v6 {bool has_deep_sleep; uint32_t num_supported, current; uint64_t frequency[RSMI_MAX_NUM_FREQUENCIES_V6];}; //? Function pointers rsmi_status_t (*rsmi_init)(uint64_t); @@ -1265,6 +1266,7 @@ namespace Gpu { LOAD_SYM(rsmi_init); LOAD_SYM(rsmi_shut_down); + LOAD_SYM(rsmi_version_get); LOAD_SYM(rsmi_num_monitor_devices); LOAD_SYM(rsmi_dev_name_get); LOAD_SYM(rsmi_dev_power_cap_get); From 69363487bc3888cb5d975409f890a4ef4b51a8a2 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Tue, 30 Jan 2024 20:12:58 -0500 Subject: [PATCH 73/91] Write newline at end of config file --- src/btop_config.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 18e60ae..3bff815 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -729,9 +729,9 @@ namespace Config { if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return; std::ofstream cwrite(conf_file, std::ios::trunc); if (cwrite.good()) { - cwrite << "#? Config file for btop v. " << Global::Version; + cwrite << "#? Config file for btop v. " << Global::Version << "\n"; for (auto [name, description] : descriptions) { - cwrite << "\n\n" << (description.empty() ? "" : description + "\n") + cwrite << "\n" << (description.empty() ? "" : description + "\n") << name << " = "; if (strings.contains(name)) cwrite << "\"" << strings.at(name) << "\""; @@ -739,6 +739,7 @@ namespace Config { cwrite << ints.at(name); else if (bools.contains(name)) cwrite << (bools.at(name) ? "True" : "False"); + cwrite << "\n"; } } } From bc0eb4291f353a97f6087b55eccc9e70ae7da58d Mon Sep 17 00:00:00 2001 From: Mathieu Sviridov <74146348+M-Sviridov@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:49:30 +1100 Subject: [PATCH 74/91] Add theme based on Everforest Dark Medium palette --- themes/everforest-dark-medium.theme | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 themes/everforest-dark-medium.theme diff --git a/themes/everforest-dark-medium.theme b/themes/everforest-dark-medium.theme new file mode 100644 index 0000000..f6aeadf --- /dev/null +++ b/themes/everforest-dark-medium.theme @@ -0,0 +1,92 @@ +# All graphs and meters can be gradients +# For single color graphs leave "mid" and "end" variable empty. +# Use "start" and "end" variables for two color gradient +# Use "start", "mid" and "end" for three color gradient + +# Main background, empty for terminal default, need to be empty if you want transparent background +theme[main_bg]="#2d353b" + +# Main text color +theme[main_fg]="#d3c6aa" + +# Title color for boxes +theme[title]="#d3c6aa" + +# Highlight color for keyboard shortcuts +theme[hi_fg]="#e67e80" + +# Background color of selected items +theme[selected_bg]="#3d484d" + +# Foreground color of selected items +theme[selected_fg]="#dbbc7f" + +# Color of inactive/disabled text +theme[inactive_fg]="#2d353b" + +# Color of text appearing on top of graphs, i.e uptime and current network graph scaling +theme[graph_text]="#d3c6aa" + +# Misc colors for processes box including mini cpu graphs, details memory graph and details status text +theme[proc_misc]="#a7c080" + +# Cpu box outline color +theme[cpu_box]="#3d484d" + +# Memory/disks box outline color +theme[mem_box]="#3d484d" + +# Net up/down box outline color +theme[net_box]="#3d484d" + +# Processes box outline color +theme[proc_box]="#3d484d" + +# Box divider line and small boxes line color +theme[div_line]="#3d484d" + +# Temperature graph colors +theme[temp_start]="#a7c080" +theme[temp_mid]="#dbbc7f" +theme[temp_end]="#f85552" + +# CPU graph colors +theme[cpu_start]="#a7c080" +theme[cpu_mid]="#dbbc7f" +theme[cpu_end]="#f85552" + +# Mem/Disk free meter +theme[free_start]="#f85552" +theme[free_mid]="#dbbc7f" +theme[free_end]="#a7c080" + +# Mem/Disk cached meter +theme[cached_start]="#7fbbb3" +theme[cached_mid]="#83c092" +theme[cached_end]="#a7c080" + +# Mem/Disk available meter +theme[available_start]="#f85552" +theme[available_mid]="#dbbc7f" +theme[available_end]="#a7c080" + +# Mem/Disk used meter +theme[used_start]="#a7c080" +theme[used_mid]="#dbbc7f" +theme[used_end]="#f85552" + +# Download graph colors +theme[download_start]="#a7c080" +theme[download_mid]="#83c092" +theme[download_end]="#7fbbb3" + +# Upload graph colors +theme[upload_start]="#dbbc7f" +theme[upload_mid]="#e69875" +theme[upload_end]="#e67e80" + +# Process box color gradient for threads, mem and cpu usage +theme[process_start]="#a7c080" +theme[process_mid]="#e67e80" +theme[process_end]="#f85552" + From 2b09f29a1e2ca86595e7e8023ba6041c319491f2 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Fri, 2 Feb 2024 09:28:07 +0100 Subject: [PATCH 75/91] fix: don't mangle memory for zombie processes for a zombie process, `proc_pidpath` returns 0, and nothing is written in fullname, so it's uninitialized garbage --- src/osx/btop_collect.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 860e457..68aa961 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -1212,10 +1212,14 @@ namespace Proc { //? Get program name, command, username, parent pid, nice and status if (no_cache) { char fullname[PROC_PIDPATHINFO_MAXSIZE]; - proc_pidpath(pid, fullname, sizeof(fullname)); - const string f_name = std::string(fullname); - size_t lastSlash = f_name.find_last_of('/'); - new_proc.name = f_name.substr(lastSlash + 1); + int rc = proc_pidpath(pid, fullname, sizeof(fullname)); + string f_name = ""; + if (rc != 0) { + f_name = std::string(fullname); + size_t lastSlash = f_name.find_last_of('/'); + f_name = f_name.substr(lastSlash + 1); + } + new_proc.name = f_name; //? Get process arguments if possible, fallback to process path in case of failure if (Shared::arg_max > 0) { std::unique_ptr proc_chars(new char[Shared::arg_max]); From f2ead3d3a9dbc78ada36a0b79068c6aa5c7023fd Mon Sep 17 00:00:00 2001 From: Laurent Cheylus Date: Thu, 8 Feb 2024 14:35:21 +0100 Subject: [PATCH 76/91] Check LC_CTYPE to set UTF-8 locale Fix aristocratos/btop#752 Signed-off-by: Laurent Cheylus --- src/btop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/btop.cpp b/src/btop.cpp index 736854f..864ecea 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -920,7 +920,7 @@ int main(int argc, char **argv) { else { string found; bool set_failure{}; - for (const auto loc_env : array{"LANG", "LC_ALL"}) { + 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) { From a0e2a5c1d2c382e269fec0f1a9d5e27b42f7d4a1 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Fri, 9 Feb 2024 12:06:16 +0100 Subject: [PATCH 77/91] cmake: Fix undefined reference to `kvm_openfiles' on BSD's --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd39946..c51687c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,11 +179,11 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") endif() find_package(devstat REQUIRED) - target_link_libraries(btop devstat::devstat) + find_package(kvm REQUIRED) + target_link_libraries(btop devstat::devstat kvm::kvm) if(BTOP_STATIC) find_package(elf REQUIRED) - find_package(kvm REQUIRED) - target_link_libraries(btop elf::elf kvm::kvm) + target_link_libraries(btop elf::elf) endif() endif() From ee46dba838da69e066232af79fe373a8533a7bc2 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Fri, 9 Feb 2024 12:42:54 +0100 Subject: [PATCH 78/91] bsd: Wrap kvm_t* in a uniq_ptr and share code between BSD's --- src/btop_shared.hpp | 20 +++++++++++++++++++- src/freebsd/btop_collect.cpp | 22 +++++----------------- src/openbsd/btop_collect.cpp | 18 +++--------------- 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 69566c6..e60da98 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -25,10 +25,19 @@ tab-size = 4 #include #include #include -#include #include #include +// From `man 3 getifaddrs`: must be included before +// clang-format off +#include +#include +// clang-format on + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +# include +#endif + using std::array; using std::atomic; using std::deque; @@ -83,6 +92,15 @@ namespace Shared { void init(); extern long coreCount, page_size, clk_tck; + +#if defined(__FreeBSD__) || defined(__OpenBSD__) + struct KvmDeleter { + void operator()(kvm_t* handle) { + kvm_close(handle); + } + }; + using KvmPtr = std::unique_ptr; +#endif } diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 2db49a2..094d78a 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -189,18 +189,6 @@ namespace Shared { Logger::debug("Init -> Mem::get_zpools()"); Mem::get_zpools(); } - - //* RAII wrapper for kvm_openfiles - class kvm_openfiles_wrapper { - kvm_t* kd = nullptr; - public: - kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) { - this->kd = kvm_openfiles(execf, coref, swapf, flags, err); - } - ~kvm_openfiles_wrapper() { kvm_close(kd); } - auto operator()() -> kvm_t* { return kd; } - }; - } // namespace Shared namespace Cpu { @@ -668,9 +656,9 @@ namespace Mem { if (show_swap) { char buf[_POSIX2_LINE_MAX]; - Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf); + Shared::KvmPtr kd {kvm_openfiles(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf)}; struct kvm_swap swap[16]; - int nswap = kvm_getswapinfo(kd(), swap, 16, 0); + int nswap = kvm_getswapinfo(kd.get(), swap, 16, 0); int totalSwap = 0, usedSwap = 0; for (int i = 0; i < nswap; i++) { totalSwap += swap[i].ksw_total; @@ -1164,8 +1152,8 @@ namespace Proc { int count = 0; char buf[_POSIX2_LINE_MAX]; - Shared::kvm_openfiles_wrapper kd(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf); - const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_PROC, 0, &count); + Shared::KvmPtr kd {kvm_openfiles(nullptr, _PATH_DEVNULL, nullptr, O_RDONLY, buf)}; + const struct kinfo_proc* kprocs = kvm_getprocs(kd.get(), KERN_PROC_PROC, 0, &count); for (int i = 0; i < count; i++) { const struct kinfo_proc* kproc = &kprocs[i]; @@ -1192,7 +1180,7 @@ namespace Proc { continue; } new_proc.name = kproc->ki_comm; - char** argv = kvm_getargv(kd(), kproc, 0); + char** argv = kvm_getargv(kd.get(), kproc, 0); if (argv) { for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { new_proc.cmd += argv[i] + " "s; diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index df35662..cc24388 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -184,18 +184,6 @@ namespace Shared { Mem::old_uptime = system_uptime(); Mem::collect(); } - - //* RAII wrapper for kvm_openfiles - class kvm_openfiles_wrapper { - kvm_t* kd = nullptr; - public: - kvm_openfiles_wrapper(const char* execf, const char* coref, const char* swapf, int flags, char* err) { - this->kd = kvm_openfiles(execf, coref, swapf, flags, err); - } - ~kvm_openfiles_wrapper() { kvm_close(kd); } - auto operator()() -> kvm_t* { return kd; } - }; - } // namespace Shared namespace Cpu { @@ -1102,8 +1090,8 @@ namespace Proc { int count = 0; char buf[_POSIX2_LINE_MAX]; - Shared::kvm_openfiles_wrapper kd(nullptr, nullptr, nullptr, KVM_NO_FILES, buf); - const struct kinfo_proc* kprocs = kvm_getprocs(kd(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count); + Shared::KvmPtr kd {kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, buf)}; + const struct kinfo_proc* kprocs = kvm_getprocs(kd.get() , KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count); for (int i = 0; i < count; i++) { const struct kinfo_proc* kproc = &kprocs[i]; @@ -1130,7 +1118,7 @@ namespace Proc { continue; } new_proc.name = kproc->p_comm; - char** argv = kvm_getargv(kd(), kproc, 0); + char** argv = kvm_getargv(kd.get(), kproc, 0); if (argv) { for (int i = 0; argv[i] and cmp_less(new_proc.cmd.size(), 1000); i++) { new_proc.cmd += argv[i] + " "s; From ab0bef204a248609ac05948e531108a8032b8e63 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Fri, 9 Feb 2024 13:53:42 +0100 Subject: [PATCH 79/91] collect: Share ifaddrs wrapper and use uniq_ptr-like syntax --- src/btop_shared.hpp | 11 +++++++++++ src/freebsd/btop_collect.cpp | 19 ++++--------------- src/linux/btop_collect.cpp | 18 ++++-------------- src/openbsd/btop_collect.cpp | 19 ++++--------------- 4 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index e60da98..8031321 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -307,6 +307,17 @@ namespace Net { bool connected{}; }; + class IfAddrsPtr { + struct ifaddrs* ifaddr; + int status; + public: + IfAddrsPtr() { status = getifaddrs(&ifaddr); } + ~IfAddrsPtr() { freeifaddrs(ifaddr); } + [[nodiscard]] constexpr auto operator()() -> struct ifaddrs* { return ifaddr; } + [[nodiscard]] constexpr auto get() -> struct ifaddrs* { return ifaddr; } + [[nodiscard]] constexpr auto get_status() const noexcept -> int { return status; }; + }; + extern std::unordered_map current_net; //* Collect net upload/download stats diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 094d78a..d64a878 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -811,17 +811,6 @@ namespace Net { bool rescale = true; uint64_t timestamp = 0; - //* RAII wrapper for getifaddrs - class getifaddr_wrapper { - struct ifaddrs *ifaddr; - - public: - int status; - getifaddr_wrapper() { status = getifaddrs(&ifaddr); } - ~getifaddr_wrapper() { freeifaddrs(ifaddr); } - auto operator()() -> struct ifaddrs * { return ifaddr; } - }; - auto collect(bool no_update) -> net_info & { auto &net = current_net; auto &config_iface = Config::getS("net_iface"); @@ -831,10 +820,10 @@ namespace Net { if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap{}; - if (if_wrap.status != 0) { + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { errors++; - Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status())); redraw = true; return empty_net; } @@ -846,7 +835,7 @@ namespace Net { string ipv4, ipv6; //? Iteration over all items in getifaddrs() list - for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { + for (auto *ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) continue; family = ifa->ifa_addr->sa_family; const auto &iface = ifa->ifa_name; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index eb8c2b7..7096ad9 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -2151,16 +2151,6 @@ namespace Net { bool rescale{true}; uint64_t timestamp{}; - //* RAII wrapper for getifaddrs - class getifaddr_wrapper { - struct ifaddrs* ifaddr; - public: - int status; - getifaddr_wrapper() { status = getifaddrs(&ifaddr); } - ~getifaddr_wrapper() { freeifaddrs(ifaddr); } - auto operator()() -> struct ifaddrs* { return ifaddr; } - }; - auto collect(bool no_update) -> net_info& { if (Runner::stopping) return empty_net; auto& net = current_net; @@ -2171,10 +2161,10 @@ namespace Net { if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap {}; - if (if_wrap.status != 0) { + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { errors++; - Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status())); redraw = true; return empty_net; } @@ -2186,7 +2176,7 @@ namespace Net { string ipv4, ipv6; //? Iteration over all items in getifaddrs() list - for (auto* ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { + for (auto* ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) continue; family = ifa->ifa_addr->sa_family; const auto& iface = ifa->ifa_name; diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index cc24388..b0d36a5 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -768,17 +768,6 @@ namespace Net { bool rescale = true; uint64_t timestamp = 0; - //* RAII wrapper for getifaddrs - class getifaddr_wrapper { - struct ifaddrs *ifaddr; - - public: - int status; - getifaddr_wrapper() { status = getifaddrs(&ifaddr); } - ~getifaddr_wrapper() { freeifaddrs(ifaddr); } - auto operator()() -> struct ifaddrs * { return ifaddr; } - }; - auto collect(bool no_update) -> net_info & { auto &net = current_net; auto &config_iface = Config::getS("net_iface"); @@ -788,10 +777,10 @@ namespace Net { if (not no_update and errors < 3) { //? Get interface list using getifaddrs() wrapper - getifaddr_wrapper if_wrap{}; - if (if_wrap.status != 0) { + IfAddrsPtr if_addrs {}; + if (if_addrs.get_status() != 0) { errors++; - Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status)); + Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_addrs.get_status())); redraw = true; return empty_net; } @@ -803,7 +792,7 @@ namespace Net { string ipv4, ipv6; //? Iteration over all items in getifaddrs() list - for (auto *ifa = if_wrap(); ifa != nullptr; ifa = ifa->ifa_next) { + for (auto *ifa = if_addrs.get(); ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) continue; family = ifa->ifa_addr->sa_family; const auto &iface = ifa->ifa_name; From 1670e1db7987957062761e86f0eb35455177b74c Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 11 Feb 2024 17:42:51 +0100 Subject: [PATCH 80/91] Reuse code from init, properly log warnings and move execution in to main loop --- src/btop.cpp | 79 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/src/btop.cpp b/src/btop.cpp index a3eaa3c..3527d75 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -109,6 +109,7 @@ namespace Global { atomic should_sleep (false); atomic _runner_started (false); atomic init_conf (false); + atomic reload_conf (false); bool arg_tty{}; bool arg_low_color{}; @@ -366,16 +367,37 @@ void _signal_handler(const int sig) { // Input::poll interrupt break; case SIGUSR2: - vector warnings; - Config::load(Config::conf_file, warnings); - Theme::setTheme(); - Draw::banner_gen(0, 0, false, true); - Draw::calcSizes(); - Runner::run("all", false, true); + Global::reload_conf = true; + Input::interrupt(); break; } } +//* Config init +void init_config(){ + atomic_lock lck(Global::init_conf); + vector load_warnings; + Config::load(Config::conf_file, load_warnings); + Config::set("lowcolor", (Global::arg_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")); + + 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 active (false); @@ -904,22 +926,7 @@ int main(int argc, char **argv) { } //? Config init - { - atomic_lock lck(Global::init_conf); - vector load_warnings; - Config::load(Config::conf_file, load_warnings); - Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor"))); - - if (Global::debug) { - Logger::set("DEBUG"); - Logger::debug("Starting in DEBUG mode!"); - } - else Logger::set(Config::getS("log_level")); - - Logger::info("Logger set to " + (Global::debug ? "DEBUG" : Config::getS("log_level"))); - - for (const auto& err_str : load_warnings) Logger::warning(err_str); - } + init_config(); //? Try to find and set a UTF-8 locale if (std::setlocale(LC_ALL, "") != nullptr and not s_contains((string)std::setlocale(LC_ALL, ""), ";") @@ -1096,9 +1103,27 @@ int main(int argc, char **argv) { 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(); } + 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(); + 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); @@ -1133,9 +1158,9 @@ int main(int argc, char **argv) { update_ms = Config::getI("update_ms"); future_time = time_ms() + update_ms; } - else if (future_time - current_time > 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(); From 9c34ac75dc7a10d1497577945bfc4b89680ca73e Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sun, 11 Feb 2024 18:32:27 +0100 Subject: [PATCH 81/91] v1.3.1 Bug fixes --- CHANGELOG.md | 24 ++++++++++++++++++++++++ src/btop.cpp | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00273c1..947d23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## v1.3.1 + +* GPU: Added support for dynamic loading of ROCm v6 libraries | @aristocratos @fxzjshm | CM #5511131 PR #737 + +* Increase max network interface name to 15 | @tessus | PR #714 + +* Fix OpenBSD UTF-8 locale detection | @lcheylus @imwints | PR #753 #717 + +* Add hot-reloading of config file with CTRL+R or SIGUSR2 signal | @MartinPit | PR #722 + +* Add battery power draw for linux and freebsd | @vsey | PR #689 + +* Fix crash caused by string exception when cpu clock is exactly between 999.5 and 999.9 Mhz | @rkmcode | PR #735 + +* Write newline at end of config file | @planet36 | PR #743 + +* Add theme based on Everforest Dark Medium palette | @M-Sviridov | PR #746 + +* fix: don't mangle memory for zombie processes | @joske | PR #747 + +* Share common code from collect | @imwints | PR #756 + +* Fixed incorrect used and available memory for OSX | CM #4461a43 + ## v1.3.0 * Added Gpu Support Linux | @romner-set | PR #529 diff --git a/src/btop.cpp b/src/btop.cpp index 5b22d79..487a562 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -79,7 +79,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.3.0"; + const string Version = "1.3.1"; int coreCount; string overlay; From 5282acc14902002e1043b7fb9064ba6d750a4a12 Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 12 Feb 2024 13:20:20 +0100 Subject: [PATCH 82/91] fix: Change bad link to old rocm repository --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 042b49b..e97b511 100644 --- a/README.md +++ b/README.md @@ -378,12 +378,12 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa * **AMD** - AMDGPU data is queried using the [ROCm SMI](https://github.com/RadeonOpenCompute/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag. + AMDGPU data is queried using the [ROCm SMI](https://github.com/rocm/rocm_smi_lib) library, which may or may not be packaged for your distribution. If your distribution doesn't provide a package, btop++ is statically linked to ROCm SMI with the `RSMI_STATIC=true` make flag. This flag expects the ROCm SMI source code in `lib/rocm_smi_lib`, and compilation will fail if it's not there. The latest tested version is 5.6.x, which can be obtained with the following command: ```bash - git clone https://github.com/RadeonOpenCompute/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib + git clone https://github.com/rocm/rocm_smi_lib.git --depth 1 -b rocm-5.6.x lib/rocm_smi_lib ```
From 43af034317bef4e3c8df748a4cd92cb50dbd4a1b Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 12 Feb 2024 13:21:55 +0100 Subject: [PATCH 83/91] fix: Can't detect librocm 6.0.x librocm reports it's version as 7.0.0.0 in the 6.0.x release series. --- src/linux/btop_collect.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0dc59a8..73e8a89 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -1344,7 +1344,8 @@ namespace Gpu { } else if (version.major == 5) { if ((rsmi_dev_gpu_clk_freq_get_v5 = (decltype(rsmi_dev_gpu_clk_freq_get_v5))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr) return false; - } else if (version.major == 6) { + // In the release tarballs of rocm 6.0.0 and 6.0.2 the version queried with rsmi_version_get is 7.0.0.0 + } else if (version.major == 6 || version.major == 7) { if ((rsmi_dev_gpu_clk_freq_get_v6 = (decltype(rsmi_dev_gpu_clk_freq_get_v6))load_rsmi_sym("rsmi_dev_gpu_clk_freq_get")) == nullptr) return false; } else { From c625d512e1c519e3b10e3355c89e213714821ae2 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 12 Feb 2024 15:31:43 +0100 Subject: [PATCH 84/91] Add check for possible librocm_smi64.so.6 and check for version 7 in the function logic --- src/linux/btop_collect.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 73e8a89..d273cc7 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -724,12 +724,12 @@ namespace Cpu { if (fs::exists(bat_dir / "power_now")) { new_bat.power_now = bat_dir / "power_now"; } - else if ((fs::exists(bat_dir / "current_now")) and (fs::exists(bat_dir / "current_now"))) { + else if ((fs::exists(bat_dir / "current_now")) and (fs::exists(bat_dir / "current_now"))) { new_bat.current_now = bat_dir / "current_now"; new_bat.voltage_now = bat_dir / "voltage_now"; - } - else { - new_bat.use_power = false; + } + else { + new_bat.use_power = false; } if (fs::exists(bat_dir / "AC0/online")) new_bat.online = bat_dir / "AC0/online"; @@ -1285,6 +1285,7 @@ namespace Gpu { "librocm_smi64.so", "librocm_smi64.so.5", // fedora "librocm_smi64.so.1.0", // debian + "librocm_smi64.so.6" }; for (const auto& l : libRocAlts) { @@ -1449,7 +1450,7 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; } else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } - else if (version_major == 6) { + else if (version.major == 6 || version.major == 7) { rsmi_frequencies_t_v6 frequencies; result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_SYS, &frequencies); if (result != RSMI_STATUS_SUCCESS) { @@ -1468,7 +1469,7 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } - else if (version_major == 6) { + else if (version.major == 6 || version.major == 7) { rsmi_frequencies_t_v6 frequencies; result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_MEM, &frequencies); if (result != RSMI_STATUS_SUCCESS) { From bae006e94c513911b274b6de221914bb066f2028 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 12 Feb 2024 15:37:31 +0100 Subject: [PATCH 85/91] Fixed variable names --- src/linux/btop_collect.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index d273cc7..d500192 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -1450,7 +1450,7 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.gpu_clock = false; } else gpus_slice[i].gpu_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } - else if (version.major == 6 || version.major == 7) { + else if (version_major == 6 || version_major == 7) { rsmi_frequencies_t_v6 frequencies; result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_SYS, &frequencies); if (result != RSMI_STATUS_SUCCESS) { @@ -1469,7 +1469,7 @@ namespace Gpu { if constexpr(is_init) gpus_slice[i].supported_functions.mem_clock = false; } else gpus_slice[i].mem_clock_speed = (long long)frequencies.frequency[frequencies.current]/1000000; // Hz to MHz } - else if (version.major == 6 || version.major == 7) { + else if (version_major == 6 || version_major == 7) { rsmi_frequencies_t_v6 frequencies; result = rsmi_dev_gpu_clk_freq_get_v6(i, RSMI_CLK_TYPE_MEM, &frequencies); if (result != RSMI_STATUS_SUCCESS) { From fd2a2acdad6fbaad76846cb5e802cf2ae022d670 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Mon, 12 Feb 2024 16:30:09 +0100 Subject: [PATCH 86/91] v1.3.2 Hotfix GPU support AMD ROCm v6 --- CHANGELOG.md | 40 +++++++++++++++++++--------------------- src/btop.cpp | 2 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 947d23d..43efe6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,24 @@ +## v1.3.2 + +Description | Author(s) | References +--- | --- | --- +fix: Can't detect librocm 6.0.x | @imwints, @aristocratos | #761 + ## v1.3.1 -* GPU: Added support for dynamic loading of ROCm v6 libraries | @aristocratos @fxzjshm | CM #5511131 PR #737 - -* Increase max network interface name to 15 | @tessus | PR #714 - -* Fix OpenBSD UTF-8 locale detection | @lcheylus @imwints | PR #753 #717 - -* Add hot-reloading of config file with CTRL+R or SIGUSR2 signal | @MartinPit | PR #722 - -* Add battery power draw for linux and freebsd | @vsey | PR #689 - -* Fix crash caused by string exception when cpu clock is exactly between 999.5 and 999.9 Mhz | @rkmcode | PR #735 - -* Write newline at end of config file | @planet36 | PR #743 - -* Add theme based on Everforest Dark Medium palette | @M-Sviridov | PR #746 - -* fix: don't mangle memory for zombie processes | @joske | PR #747 - -* Share common code from collect | @imwints | PR #756 - -* Fixed incorrect used and available memory for OSX | CM #4461a43 +Description | Author(s) | References +--- | --- | --- +GPU: Added support for dynamic loading of ROCm v6 libraries | @aristocratos, @fxzjshm | 5511131, #737 +Increase max network interface name to 15 | @tessus | #714 +Fix OpenBSD UTF-8 locale detection | @lcheylus, @imwints | #753, #717 +Add hot-reloading of config file with CTRL+R or SIGUSR2 signal | @MartinPit | #722 +Add battery power draw for linux and freebsd | @vsey | #689 +Fix crash caused by string exception when cpu clock is exactly between 999.5 and 999.9 Mhz | @rkmcode | #735 +Write newline at end of config file | @planet36 | #743 +Add theme based on Everforest Dark Medium palette | @M-Sviridov | #746 +fix: don't mangle memory for zombie processes | @joske | #747 +Share common code from collect | @imwints | #756 +Fixed incorrect used and available memory for OSX | | 4461a43 ## v1.3.0 diff --git a/src/btop.cpp b/src/btop.cpp index 487a562..891fefe 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -79,7 +79,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.3.1"; + const string Version = "1.3.2"; int coreCount; string overlay; From 83739f84e1f4bf66c3f63666985c0ffd65beabfc Mon Sep 17 00:00:00 2001 From: Steffen Winter Date: Mon, 12 Feb 2024 16:19:35 +0100 Subject: [PATCH 87/91] cmake: Remove version specifier --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 009a319..d17f88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") endif() project("btop" - VERSION 1.3.0 DESCRIPTION "A monitor of resources" HOMEPAGE_URL "https://github.com/aristocratos/btop" LANGUAGES CXX From e031cce6bae9e42e5836c664afa5256e7a858f10 Mon Sep 17 00:00:00 2001 From: David Mak Date: Wed, 14 Feb 2024 00:59:08 +0800 Subject: [PATCH 88/91] collect: Fix reading of battery power draw on Linux This was erroneously set to read from the current battery charge. Fixes #770. --- src/linux/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index d500192..1799b0d 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -835,7 +835,7 @@ namespace Cpu { if (b.use_power) { if (not b.power_now.empty()) { try { - watts = (float)stoll(readfile(b.energy_now, "-1")) / 1000000.0; + watts = (float)stoll(readfile(b.power_now, "-1")) / 1000000.0; } catch (const std::invalid_argument&) { } catch (const std::out_of_range&) { } From bfe8c20ebe3d180f7e0b6b2ac016c749ad3a996a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Kek=C3=A4l=C3=A4inen?= Date: Sat, 13 Jan 2024 18:26:02 -0800 Subject: [PATCH 89/91] Fix misc spelling --- CHANGELOG.md | 4 ++-- CMakeLists.txt | 2 +- README.md | 8 ++++---- src/btop.cpp | 4 ++-- src/btop_input.hpp | 2 +- src/btop_menu.cpp | 4 ++-- src/btop_theme.hpp | 2 +- src/btop_tools.cpp | 4 ++-- src/btop_tools.hpp | 4 ++-- src/freebsd/btop_collect.cpp | 4 ++-- src/linux/btop_collect.cpp | 4 ++-- src/openbsd/btop_collect.cpp | 2 +- src/osx/btop_collect.cpp | 2 +- themes/adapta.theme | 2 +- themes/adwaita.theme | 2 +- themes/dusklight.theme | 2 +- themes/elementarish.theme | 2 +- themes/flat-remix-light.theme | 2 +- themes/flat-remix.theme | 2 +- themes/greyscale.theme | 2 +- themes/gruvbox_dark.theme | 2 +- themes/gruvbox_material_dark.theme | 2 +- themes/matcha-dark-sea.theme | 2 +- themes/monokai.theme | 2 +- themes/night-owl.theme | 2 +- themes/nord.theme | 2 +- themes/onedark.theme | 2 +- themes/paper.theme | 2 +- themes/solarized_dark.theme | 2 +- themes/solarized_light.theme | 2 +- themes/tokyo-night.theme | 2 +- themes/tokyo-storm.theme | 2 +- themes/tomorrow-night.theme | 2 +- themes/whiteout.theme | 2 +- 34 files changed, 44 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43efe6f..d2c4bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -180,7 +180,7 @@ Fixed incorrect used and available memory for OSX | | 4461a43 * Fixed: Wrong memory unit when shorten and size is less than 10, by @mohi001 -* Fixed: Use cpu cores avarage temp if missing cpu package temp for FreeBSD +* Fixed: Use cpu cores average temp if missing cpu package temp for FreeBSD * Changed: Enter symbol to a more common variant @@ -476,7 +476,7 @@ Fixed incorrect used and available memory for OSX | | 4461a43 * Fixed: Sizing constraints bug on start and boxes can be toggled from size error screen -* Fixed: UTF-8 check crashing if LANG was set to non existant locale +* Fixed: UTF-8 check crashing if LANG was set to non existent locale ## v1.0.4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8353a5b..01b3708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,7 +147,7 @@ if(LINUX AND BTOP_GPU) if(BTOP_RSMI_STATIC) # ROCm doesn't properly add it's folders to the module path if `CMAKE_MODULE_PATH` is already # set - # We could also manully append ROCm's path here + # We could also manually append ROCm's path here set(_CMAKE_MODULE_PATH CMAKE_MODULE_PATH) unset(CMAKE_MODULE_PATH) diff --git a/README.md b/README.md index e97b511..5e01373 100644 --- a/README.md +++ b/README.md @@ -425,7 +425,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa | `GPU_SUPPORT=` | Enable/disable GPU support (Enabled by default on X86_64 Linux) | | `RSMI_STATIC=true` | To statically link the ROCm SMI library used for querying AMDGPU | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manually set which compiler to use | Example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. @@ -596,7 +596,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa | `ARCH=` | To manually set the target architecture | | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manually set which compiler to use | Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. @@ -764,7 +764,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa | `ARCH=` | To manually set the target architecture | | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manually set which compiler to use | Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. @@ -945,7 +945,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa | `ARCH=` | To manually set the target architecture | | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | | `ADDFLAGS=` | For appending flags to both compiler and linker | - | `CXX=` | Manualy set which compiler to use | + | `CXX=` | Manually set which compiler to use | Example: `gmake ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. diff --git a/src/btop.cpp b/src/btop.cpp index 5240043..8eae107 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -452,7 +452,7 @@ namespace Runner { } }; - //* Wrapper for raising priviliges when using SUID bit + //* Wrapper for raising privileges when using SUID bit class gain_priv { int status = -1; public: @@ -885,7 +885,7 @@ int main(int argc, char **argv) { Global::start_time = time_s(); - //? Save real and effective userid's and drop priviliges until needed if running with SUID bit set + //? 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) { diff --git a/src/btop_input.hpp b/src/btop_input.hpp index 13f14fe..91b9dc9 100644 --- a/src/btop_input.hpp +++ b/src/btop_input.hpp @@ -56,7 +56,7 @@ namespace Input { //* Last entered key extern deque history; - //* Poll keyboard & mouse input for ms and return input availabilty as a bool + //* Poll keyboard & mouse input for ms and return input availability as a bool bool poll(const uint64_t timeout=0); //* Get a key or mouse action from input diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index fbf07c2..bf57e8c 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -490,7 +490,7 @@ namespace Menu { "Kelvin, 0 = absolute zero, 1 degree change", "equals 1 degree change in Celsius.", "", - "Rankine, 0 = abosulte zero, 1 degree change", + "Rankine, 0 = absolute zero, 1 degree change", "equals 1 degree change in Fahrenheit."}, {"show_cpu_freq", "Show CPU frequency.", @@ -651,7 +651,7 @@ namespace Menu { "", "Begin line with \"exclude=\" to change to", "exclude filter.", - "Oterwise defaults to \"most include\" filter.", + "Otherwise defaults to \"most include\" filter.", "", "Example:", "\"exclude=/boot /home/user\""}, diff --git a/src/btop_theme.hpp b/src/btop_theme.hpp index cc3fbdf..dcc00e4 100644 --- a/src/btop_theme.hpp +++ b/src/btop_theme.hpp @@ -32,7 +32,7 @@ namespace Theme { extern std::filesystem::path theme_dir; extern std::filesystem::path user_theme_dir; - //* Contains "Default" and "TTY" at indeces 0 and 1, otherwise full paths to theme files + //* Contains "Default" and "TTY" at indices 0 and 1, otherwise full paths to theme files extern vector themes; //* Generate escape sequence for 24-bit or 256 color and return as a string diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 8a85ed4..d1351e4 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -89,7 +89,7 @@ namespace Term { } bool refresh(bool only_check) { - // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't avaiable. + // Query dimensions of '/dev/tty' of the 'STDOUT_FILENO' isn't available. // This variable is set in those cases to avoid calls to ioctl constinit static bool uses_dev_tty = false; struct winsize wsize {}; @@ -644,7 +644,7 @@ namespace Logger { size_t loglevel; fs::path logfile; - //* Wrapper for lowering priviliges if using SUID bit and currently isn't using real userid + //* Wrapper for lowering privileges if using SUID bit and currently isn't using real userid class lose_priv { int status = -1; public: diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 2502786..07f7de3 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -114,7 +114,7 @@ namespace Mv { //* Save cursor position const string save = Fx::e + "s"; - //* Restore saved cursor postion + //* Restore saved cursor position const string restore = Fx::e + "u"; } @@ -286,7 +286,7 @@ namespace Tools { return is_in(str, "true", "True"); } - //* Check if a string is a valid integer value (only postive) + //* Check if a string is a valid integer value (only positive) inline bool isint(const string& str) { return all_of(str.begin(), str.end(), ::isdigit); } diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index decd542..a99f8df 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -265,7 +265,7 @@ namespace Cpu { got_sensors = true; int temp; size_t size = sizeof(temp); - sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, nullptr, 0); //asuming the max temp is same for all cores + sysctlbyname("dev.cpu.0.coretemp.tjmax", &temp, &size, nullptr, 0); //assuming the max temp is same for all cores temp = (temp - 2732) / 10; // since it's an int, it's multiplied by 10, and offset to absolute zero... current_cpu.temp_max = temp; } @@ -907,7 +907,7 @@ namespace Net { } } - //? Get total recieved and transmitted bytes + device address if no ip was found + //? Get total received and transmitted bytes + device address if no ip was found for (const auto &iface : interfaces) { for (const string dir : {"download", "upload"}) { auto &saved_stat = net.at(iface).stat.at(dir); diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 1799b0d..c549872 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -2061,7 +2061,7 @@ namespace Mem { if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { return zfs_pool_stat_path; } else { - Logger::debug("Cant access folder: " + zfs_pool_stat_path.string()); + Logger::debug("Can't access folder: " + zfs_pool_stat_path.string()); return ""; } } @@ -2266,7 +2266,7 @@ namespace Net { } //else, ignoring family==AF_PACKET (see man 3 getifaddrs) which is the first one in the `for` loop. } - //? Get total recieved and transmitted bytes + device address if no ip was found + //? Get total received and transmitted bytes + device address if no ip was found for (const auto& iface : interfaces) { if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty()) net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address"); diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index bdcb105..9b3e1f2 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -859,7 +859,7 @@ namespace Net { } } - //? Get total recieved and transmitted bytes + device address if no ip was found + //? Get total received and transmitted bytes + device address if no ip was found for (const auto &iface : interfaces) { for (const string dir : {"download", "upload"}) { auto &saved_stat = net.at(iface).stat.at(dir); diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index df1fca3..a2c4f62 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -940,7 +940,7 @@ namespace Net { } } - //? Get total recieved and transmitted bytes + device address if no ip was found + //? Get total received and transmitted bytes + device address if no ip was found for (const auto &iface : interfaces) { for (const string dir : {"download", "upload"}) { auto &saved_stat = net.at(iface).stat.at(dir); diff --git a/themes/adapta.theme b/themes/adapta.theme index e3e2128..4448228 100644 --- a/themes/adapta.theme +++ b/themes/adapta.theme @@ -18,7 +18,7 @@ theme[main_fg]="#cfd8dc" # Title color for boxes theme[title]="#ff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/adwaita.theme b/themes/adwaita.theme index 25e6bbf..97a52bc 100644 --- a/themes/adwaita.theme +++ b/themes/adwaita.theme @@ -18,7 +18,7 @@ theme[main_fg]="#2e3436" # Title color for boxes theme[title]="#2e3436" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#1a5fb4" # Background color of selected item in processes box diff --git a/themes/dusklight.theme b/themes/dusklight.theme index be60f1a..01e5ae6 100644 --- a/themes/dusklight.theme +++ b/themes/dusklight.theme @@ -18,7 +18,7 @@ theme[main_fg]="#99DFFF" # Title color for boxes theme[title]="#99FFFF" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#FF7F00" # Background color of selected item in processes box diff --git a/themes/elementarish.theme b/themes/elementarish.theme index 0fe5398..436b6f8 100644 --- a/themes/elementarish.theme +++ b/themes/elementarish.theme @@ -11,7 +11,7 @@ theme[main_fg]="#eee8d5" # Title color for boxes theme[title]="#eee8d5" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#d1302c" # Background color of selected item in processes box diff --git a/themes/flat-remix-light.theme b/themes/flat-remix-light.theme index db1afe4..8344a96 100644 --- a/themes/flat-remix-light.theme +++ b/themes/flat-remix-light.theme @@ -18,7 +18,7 @@ theme[main_fg]="#737680" # Title color for boxes theme[title]="#272a34" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/flat-remix.theme b/themes/flat-remix.theme index 54585e7..0a53a95 100644 --- a/themes/flat-remix.theme +++ b/themes/flat-remix.theme @@ -18,7 +18,7 @@ theme[main_fg]="#E6E6E6" # Title color for boxes theme[title]="#ff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/greyscale.theme b/themes/greyscale.theme index 442a82f..aee12af 100644 --- a/themes/greyscale.theme +++ b/themes/greyscale.theme @@ -18,7 +18,7 @@ theme[main_fg]="#bb" # Title color for boxes theme[title]="#cc" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#90" # Background color of selected item in processes box diff --git a/themes/gruvbox_dark.theme b/themes/gruvbox_dark.theme index feed893..8cd9d37 100644 --- a/themes/gruvbox_dark.theme +++ b/themes/gruvbox_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#a89984" # Title color for boxes theme[title]="#ebdbb2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#d79921" # Background color of selected items diff --git a/themes/gruvbox_material_dark.theme b/themes/gruvbox_material_dark.theme index 749c7a7..5dc5822 100644 --- a/themes/gruvbox_material_dark.theme +++ b/themes/gruvbox_material_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#d4be98" # Title color for boxes theme[title]="#d4be98" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#ea6962" # Background color of selected items diff --git a/themes/matcha-dark-sea.theme b/themes/matcha-dark-sea.theme index 34889ff..07a0b38 100644 --- a/themes/matcha-dark-sea.theme +++ b/themes/matcha-dark-sea.theme @@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2" # Title color for boxes theme[title]="#F8F8F2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#2eb398" # Background color of selected item in processes box diff --git a/themes/monokai.theme b/themes/monokai.theme index f379dc9..29299f5 100644 --- a/themes/monokai.theme +++ b/themes/monokai.theme @@ -18,7 +18,7 @@ theme[main_fg]="#F8F8F2" # Title color for boxes theme[title]="#F8F8F2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#F92672" # Background color of selected item in processes box diff --git a/themes/night-owl.theme b/themes/night-owl.theme index 7537fea..0de70ae 100644 --- a/themes/night-owl.theme +++ b/themes/night-owl.theme @@ -18,7 +18,7 @@ theme[main_fg]="#d6deeb" # Title color for boxes theme[title]="#ffffff" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#addb67" # Background color of selected items diff --git a/themes/nord.theme b/themes/nord.theme index 07c43c2..fbd0af1 100644 --- a/themes/nord.theme +++ b/themes/nord.theme @@ -18,7 +18,7 @@ theme[main_fg]="#D8DEE9" # Title color for boxes theme[title]="#8FBCBB" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#5E81AC" # Background color of selected item in processes box diff --git a/themes/onedark.theme b/themes/onedark.theme index f4441de..5a5ad01 100644 --- a/themes/onedark.theme +++ b/themes/onedark.theme @@ -10,7 +10,7 @@ theme[main_fg]="#abb2bf" # Title color for boxes theme[title]="#abb2bf" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#61afef" # Background color of selected item in processes box diff --git a/themes/paper.theme b/themes/paper.theme index 111289d..00f96a2 100644 --- a/themes/paper.theme +++ b/themes/paper.theme @@ -12,7 +12,7 @@ theme[main_fg]="#00" # Title color for boxes theme[title]="#00" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#CC3E28" # Background color of selected item in processes box diff --git a/themes/solarized_dark.theme b/themes/solarized_dark.theme index 6069823..2b3c62b 100644 --- a/themes/solarized_dark.theme +++ b/themes/solarized_dark.theme @@ -18,7 +18,7 @@ theme[main_fg]="#eee8d5" # Title color for boxes theme[title]="#fdf6e3" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#b58900" # Background color of selected items diff --git a/themes/solarized_light.theme b/themes/solarized_light.theme index 9478410..c786ce2 100644 --- a/themes/solarized_light.theme +++ b/themes/solarized_light.theme @@ -18,7 +18,7 @@ theme[main_fg]="#586e75" # Title color for boxes theme[title]="#002b36" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#b58900" # Background color of selected items diff --git a/themes/tokyo-night.theme b/themes/tokyo-night.theme index aae6a3b..b8200ec 100644 --- a/themes/tokyo-night.theme +++ b/themes/tokyo-night.theme @@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2" # Title color for boxes theme[title]="#cfc9c2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#7dcfff" # Background color of selected item in processes box diff --git a/themes/tokyo-storm.theme b/themes/tokyo-storm.theme index a4edfcf..e987f1c 100644 --- a/themes/tokyo-storm.theme +++ b/themes/tokyo-storm.theme @@ -10,7 +10,7 @@ theme[main_fg]="#cfc9c2" # Title color for boxes theme[title]="#cfc9c2" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#7dcfff" # Background color of selected item in processes box diff --git a/themes/tomorrow-night.theme b/themes/tomorrow-night.theme index a745005..5900044 100644 --- a/themes/tomorrow-night.theme +++ b/themes/tomorrow-night.theme @@ -18,7 +18,7 @@ theme[main_fg]="#c5c8c6" # Title color for boxes theme[title]="#c5c8c6" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#81beb7" # Background color of selected item in processes box diff --git a/themes/whiteout.theme b/themes/whiteout.theme index 23c8ebe..891bb7d 100644 --- a/themes/whiteout.theme +++ b/themes/whiteout.theme @@ -18,7 +18,7 @@ theme[main_fg]="#30" # Title color for boxes theme[title]="#10" -# Higlight color for keyboard shortcuts +# Highlight color for keyboard shortcuts theme[hi_fg]="#284d75" # Background color of selected item in processes box From ed68589af16dde7e7c4e1e01f01b0969d2eb0565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Kek=C3=A4l=C3=A4inen?= Date: Wed, 17 Jan 2024 21:10:48 -0800 Subject: [PATCH 90/91] Create man page for btop in Markdown Create a man page in Markdown format so that it can be read online on GitHub etc and it can be edited much more easily than a raw troff/groff file. Compile it to proper man page format at build-time using 'lowdown' if it is available on the system, otherwise just issue a warning in yellow. Tested to work both with: export VERBOSE=1 make make install make uninstall cmake -B build cmake --build build --verbose While Lowdown is easy to manually install in all modern Linux distros and also Homebrew for Mac, this commit does not add 'lowdown' in any build dependencies or in the CI, that needs to be done separately. --- .gitignore | 3 +++ CMakeLists.txt | 17 ++++++++++++++- Makefile | 31 ++++++++++++++++++++------- README.md | 30 +++++++++++++------------- manpage.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 manpage.md diff --git a/.gitignore b/.gitignore index c31e456..3a2cae2 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ stage/ *.out *.app +# Compiled man page +btop.1 + build bin btop diff --git a/CMakeLists.txt b/CMakeLists.txt index 01b3708..9a4dbec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,9 +210,24 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") target_link_libraries(btop kvm::kvm) endif() + +# Check if lowdown is installed +find_program(LOWDOWN_EXECUTABLE lowdown) + +if(LOWDOWN_EXECUTABLE) + # Custom target to compile Markdown to man page using lowdown + add_custom_target(btop.1 ALL + COMMAND lowdown -s -Tman -o btop.1 manpage.md + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + # Install the man page + install(FILES btop.1 DESTINATION "share/man/man1") +else() + message(WARNING "Command 'lowdown' not found: skipping generating man page btop.1") +endif() + install(TARGETS btop RUNTIME) install(FILES "btop.desktop" DESTINATION "share/applications") install(FILES "Img/icon.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "btop.png") install(FILES "Img/icon.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "btop.svg") install(DIRECTORY "themes" DESTINATION "share/btop") - diff --git a/Makefile b/Makefile index 03aea89..e489164 100644 --- a/Makefile +++ b/Makefile @@ -228,7 +228,7 @@ endif #? Default Make .ONESHELL: -all: | info rocm_smi info-quiet directories config.h btop +all: | info rocm_smi info-quiet directories btop.1 config.h btop ifneq ($(QUIET),true) info: @@ -249,7 +249,6 @@ info: @true endif - info-quiet: | info rocm_smi @printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n" @@ -280,6 +279,15 @@ $(BUILDDIR)/config.h: $(SRCDIR)/config.h.in | directories @$(VERBOSE) || printf 'sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null\n' @sed -e "s|@GIT_COMMIT@|$(GIT_COMMIT)|" -e "s|@CONFIGURE_COMMAND@|$(CONFIGURE_COMMAND)|" -e "s|@COMPILER@|$(CXX)|" -e "s|@COMPILER_VERSION@|$(CXX_VERSION)|" $< | tee $@ > /dev/null +#? Man page +btop.1: manpage.md | directories +ifeq ($(shell command -v lowdown >/dev/null; echo $$?),0) + @printf "\n\033[1;92mGenerating man page $@\033[37m...\033[0m\n" + lowdown -s -Tman -o $@ $< +else + @printf "\n\033[1;93mCommand 'lowdown' not found: skipping generating man page $@\033[0m\n" +endif + #? Clean only Objects clean: @printf "\033[1;91mRemoving: \033[1;97mbuilt objects...\033[0m\n" @@ -311,7 +319,11 @@ install: @printf "\033[1;92mInstalling SVG icon to: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\n" @mkdir -p $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps @cp -p Img/icon.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg - +ifneq ($(wildcard btop.1),) + @printf "\033[1;92mInstalling man page to: \033[1;97m$(DESTDIR)$(PREFIX)/share/man/man1/btop.1\n" + @mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1 + @cp -p btop.1 $(DESTDIR)$(PREFIX)/share/man/man1/btop.1 +endif #? Set SUID bit for btop as $SU_USER in $SU_GROUP setuid: @@ -321,17 +333,20 @@ setuid: @printf "\033[1;92mSetting SUID bit\033[0m\n" @chmod u+s $(DESTDIR)$(PREFIX)/bin/btop +# With 'rm -v' user will see what files (if any) got removed uninstall: @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/bin/btop\033[0m\n" - @rm -rf $(DESTDIR)$(PREFIX)/bin/btop + @rm -rfv $(DESTDIR)$(PREFIX)/bin/btop @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\033[0m\n" - @rm -rf $(DESTDIR)$(PREFIX)/share/btop + @rm -rfv $(DESTDIR)$(PREFIX)/share/btop @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/applications/btop.desktop\033[0m\n" - @rm -rf $(DESTDIR)$(PREFIX)/share/applications/btop.desktop + @rm -rfv $(DESTDIR)$(PREFIX)/share/applications/btop.desktop @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png\033[0m\n" - @rm -rf $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png + @rm -rfv $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/btop.png @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg\033[0m\n" - @rm -rf $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg + @rm -rfv $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/btop.svg + @printf "\033[1;91mRemoving: \033[1;97m$(DESTDIR)$(PREFIX)/share/man/man1/btop.1\033[0m\n" + @rm -rfv $(DESTDIR)$(PREFIX)/share/man/man1/btop.1 #? Pull in dependency info for *existing* .o files -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) diff --git a/README.md b/README.md index 5e01373..fdd4c31 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ If you want to help out, test for bugs/fix bugs or just try out the branches: **macOS / OSX** ```bash # Install and use Homebrew or MacPorts package managers for easy dependency installation -brew install coreutils make gcc@11 +brew install coreutils make gcc@11 lowdown git clone https://github.com/aristocratos/btop.git cd btop git checkout OSX @@ -115,7 +115,7 @@ gmake **FreeBSD** ```bash -sudo pkg install gmake gcc11 coreutils git +sudo pkg install gmake gcc11 coreutils git lowdown git clone https://github.com/aristocratos/btop.git cd btop git checkout freebsd @@ -395,7 +395,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install dependencies (example for Ubuntu 21.04 Hirsute)** ```bash - sudo apt install coreutils sed git build-essential gcc-11 g++-11 + sudo apt install coreutils sed git build-essential gcc-11 g++-11 lowdown ``` 2. **Clone repository** @@ -486,12 +486,12 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install build dependencies** - Requires Clang / GCC, CMake, Ninja and Git + Requires Clang / GCC, CMake, Ninja, Lowdown and Git For example, with Debian Bookworm: ```bash - sudo apt install cmake git g++ ninja-build + sudo apt install cmake git g++ ninja-build lowdown ``` 2. **Clone the repository** @@ -569,7 +569,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install dependencies (example for Homebrew)** ```bash - brew install coreutils make gcc@12 + brew install coreutils make gcc@12 lowdown ``` 2. **Clone repository** @@ -655,11 +655,11 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install build dependencies** - Requires Clang, CMake, Ninja and Git + Requires Clang, CMake, Ninja, Lowdown and Git ```bash brew update --quiet - brew install cmake git llvm ninja + brew install cmake git llvm ninja lowdown ``` 2. **Clone the repository** @@ -736,7 +736,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install dependencies** ```bash - sudo pkg install gmake gcc11 coreutils git + sudo pkg install gmake gcc11 coreutils git lowdown ``` 2. **Clone repository** @@ -823,18 +823,18 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install build dependencies** - Requires Clang / GCC, CMake, Ninja and Git + Requires Clang / GCC, CMake, Ninja, Lowdown and Git _**Note:** LLVM's libc++ shipped with FreeBSD 13 is too old and cannot compile btop._ FreeBSD 14 and later: ```bash - pkg install cmake ninja + pkg install cmake ninja lowdown ``` FreeBSD 13: ```bash - pkg install cmake gcc13 ninja + pkg install cmake gcc13 ninja lowdown ``` 2. **Clone the repository** @@ -917,7 +917,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install dependencies** ```bash - pkg_add gmake gcc%11 g++%11 coreutils git + pkg_add gmake gcc%11 g++%11 coreutils git lowdown ``` 2. **Clone repository** @@ -1004,12 +1004,12 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa 1. **Install build dependencies** - Requires GCC, CMake, Ninja and Git + Requires GCC, CMake, Ninja, Lowdown and Git _**Note:** LLVM's libc++ shipped with OpenBSD 7.4 is too old and cannot compile btop._ ```bash - pkg_add cmake g++%11 git ninja + pkg_add cmake g++%11 git ninja lowdown ``` 2. **Clone the repository** diff --git a/manpage.md b/manpage.md new file mode 100644 index 0000000..105a7c5 --- /dev/null +++ b/manpage.md @@ -0,0 +1,57 @@ +% btop(1) | User Commands +% +% "January 4 2024" + +# NAME + +btop - Resource monitor that shows usage and stats for processor, memory, disks, network, and processes. + +# SYNOPSIS + +**btop** [**-lc**] [**-t** | **+t**] [**-p** _id_] [**\-\-utf-force**] + [**\-\-debug**] [{**-h** | **\-\-help**} | {**-v** | **\-\-version**}] + +# DESCRIPTION + +**btop** is a program that shows usage and stats for processor, memory, disks, network, and processes. + +# OPTIONS + +The program follows the usual GNU command line syntax, with long options +starting with two dashes ('-'). A summary of options is included below. + +**-lc**, **\-\-low-color** +: Disable truecolor, converts 24-bit colors to 256-color. + +**-t**, **\-\-tty_on** +: Force (ON) tty mode, max 16 colors and tty-friendly graph symbols. + +**+t**, **\-\-tty_off** +: Force (OFF) tty mode. + +**-p**, **\-\-preset _id_** +: Start with preset, integer value between 0-9. + +**\-\-utf-force** +: Force start even if no UTF-8 locale was detected. + +**\-\-debug** +: Start in DEBUG mode: shows microsecond timer for information collect and screen draw functions and sets loglevel to DEBUG. + +**-h**, **\-\-help** +: Show summary of options. + +**-v**, **\-\-version** +: Show version of program. + +# BUGS + +The upstream bug tracker can be found at https://github.com/aristocratos/btop/issues. + +# SEE ALSO + +**top**(1), **htop**(1) + +# AUTHOR + +**btop** was written by Jakob P. Liljenberg a.k.a. "Aristocratos". From d8c054d92fac3a24a1bcc1bd0a931ae207b84032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Kek=C3=A4l=C3=A4inen?= Date: Sat, 2 Mar 2024 19:28:16 -0800 Subject: [PATCH 91/91] Add 'lowdown' to build dependencies so GitHub actions actually runs it Building the man page is optional and happens automatically if command 'lowdown' is present on the system. Add it to all possible GitHub CI files so man page conversion will be tested and fully used. Unfortunately 'lowdown' cannot be added to the musl jobs the program is available only starting from Alpine v3.15, while the musl Docker images run Alpine v3.14 (and haven't been updated in 2+ years). Also, the Snap build used Ubuntu 20.04 "Focal" which equally is too old to include Lowdown, which is available only from Ubuntu 22.04 "Jammy" onward: https://packages.ubuntu.com/search?suite=all&searchon=names&keywords=lowdown --- .github/workflows/cmake-freebsd.yml | 3 +-- .github/workflows/cmake-linux.yml | 3 +-- .github/workflows/cmake-macos.yml | 3 +-- .github/workflows/continuous-build-freebsd.yml | 3 +-- .github/workflows/continuous-build-openbsd.yml | 3 +-- snap/snapcraft.yaml | 4 +++- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cmake-freebsd.yml b/.github/workflows/cmake-freebsd.yml index 6e687f1..3f1c962 100644 --- a/.github/workflows/cmake-freebsd.yml +++ b/.github/workflows/cmake-freebsd.yml @@ -33,8 +33,7 @@ jobs: with: release: '14.0' usesh: true - prepare: pkg install -y cmake ninja + prepare: pkg install -y cmake ninja lowdown run: | CXX=clang++ cmake -B build -G Ninja -DBTOP_STATIC=ON cmake --build build --verbose - diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 49754d9..7864a04 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -30,11 +30,10 @@ jobs: - uses: actions/checkout@v4 - name: Install build tools - run: apk add --no-cache --update clang cmake lld ninja + run: apk add --no-cache --update clang cmake lld ninja lowdown - name: Configure run: CXX=clang++ LDFLAGS=-fuse-ld=lld cmake -B build -G Ninja -DBTOP_STATIC=ON - name: Compile run: cmake --build build --verbose - diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml index 32d6f7f..ee2ef97 100644 --- a/.github/workflows/cmake-macos.yml +++ b/.github/workflows/cmake-macos.yml @@ -32,7 +32,7 @@ jobs: run: | export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update --quiet - brew install --force --overwrite cmake llvm@17 ninja + brew install --force --overwrite cmake llvm@17 ninja lowdown - name: Configure run: | @@ -44,4 +44,3 @@ jobs: - name: Compile run: cmake --build build --verbose - diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index d06190f..5521ff7 100644 --- a/.github/workflows/continuous-build-freebsd.yml +++ b/.github/workflows/continuous-build-freebsd.yml @@ -45,7 +45,7 @@ jobs: release: '14.0' usesh: true prepare: | - pkg install -y gmake gcc coreutils git + pkg install -y gmake gcc coreutils git lowdown git config --global --add safe.directory /home/runner/work/btop/btop run: | CXX=${{ matrix.compiler }} gmake STATIC=true STRIP=true @@ -59,4 +59,3 @@ jobs: name: btop-x86_64-freebsd-14 path: 'bin/*' if-no-files-found: error - diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml index afb1a93..6925ebb 100644 --- a/.github/workflows/continuous-build-openbsd.yml +++ b/.github/workflows/continuous-build-openbsd.yml @@ -42,7 +42,7 @@ jobs: release: '7.4' usesh: true prepare: | - pkg_add gmake gcc%11 g++%11 coreutils git + pkg_add gmake gcc%11 g++%11 coreutils git lowdown git config --global --add safe.directory /home/runner/work/btop/btop run: | gmake CXX=eg++ STATIC=true STRIP=true @@ -55,4 +55,3 @@ jobs: name: btop-x86_64-openbsd-7.4 path: 'bin/*' if-no-files-found: error - diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2e76ba5..61d8d1b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -49,6 +49,8 @@ parts: - PREFIX=/usr/local - STATIC=true - ADDFLAGS="-D SNAPPED" + # Add 'lowdown' to build dependencies as soon at Snap builder updates from + # Ubuntu 20.04 "Focal" to something newer that has Lowdown included build-packages: - coreutils - sed @@ -56,7 +58,7 @@ parts: - build-essential - gcc-11 - g++-11 - + override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)"