From 57c41152827a1ff76d9c26ceeb235c2fe4da3006 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Sat, 6 Jan 2024 08:28:11 +0100 Subject: [PATCH 01/15] Add NetBSD Support. --- Makefile | 6 + src/btop.cpp | 6 +- src/netbsd/btop_collect.cpp | 1284 +++++++++++++++++++++++++++++++++++ 3 files changed, 1293 insertions(+), 3 deletions(-) create mode 100644 src/netbsd/btop_collect.cpp diff --git a/Makefile b/Makefile index e489164..c1d4cec 100644 --- a/Makefile +++ b/Makefile @@ -146,6 +146,12 @@ else ifeq ($(PLATFORM_LC),openbsd) override ADDFLAGS += -lkvm -static-libstdc++ export MAKE = gmake SU_GROUP := wheel +else ifeq ($(PLATFORM_LC),netbsd) + PLATFORM_DIR := netbsd + 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 8eae107..9ee495a 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -293,7 +293,7 @@ void clean_quit(int sig) { Global::quitting = true; Runner::stop(); if (Global::_runner_started) { - #if defined __APPLE__ || defined __OpenBSD__ + #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__ if (pthread_join(Runner::runner_id, nullptr) != 0) { Logger::warning("Failed to join _runner thread on exit!"); pthread_cancel(Runner::runner_id); @@ -329,7 +329,7 @@ void clean_quit(int sig) { const auto excode = (sig != -1 ? sig : 0); -#if defined __APPLE__ || defined __OpenBSD__ +#if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__ _Exit(excode); #else quick_exit(excode); @@ -1024,7 +1024,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"); } -#if not defined __APPLE__ && not defined __OpenBSD__ +#if not defined __APPLE__ && not defined __OpenBSD__ && not defined __NetBSD__ 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/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp new file mode 100644 index 0000000..6296b9b --- /dev/null +++ b/src/netbsd/btop_collect.cpp @@ -0,0 +1,1284 @@ +/* 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 +#include + +#include "../btop_config.hpp" +#include "../btop_shared.hpp" +#include "../btop_tools.hpp" + +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; + std::unordered_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"}; + + std::unordered_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, 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"), 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() -> std::unordered_map { + std::unordered_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.0, 0, ""}; + + long seconds = -1; + uint32_t percent = -1; + string status = "discharging"; + int64_t full, remaining; + full = get_sensor("acpibat0", 0); + remaining = get_sensor("acpibat0", 3); + int64_t state = get_sensor("acpibat0", 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, 0.0, 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"); + } + + vector> cpu_time(Shared::coreCount); + size_t size = sizeof(long) * CPUSTATES * Shared::coreCount; + if (sysctlbyname("kern.cp_time", &cpu_time[0], &size, nullptr, 0) == -1) { + Logger::error("failed to get CPU time"); + } + 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 = cpu_time[i][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 current_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(std::unordered_map &disks, std::unordered_map &mapping) { + uint64_t total_bytes_read = 0; + uint64_t total_bytes_write = 0; + + int num_drives = 0; + int mib[3] = { CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)}; + + size_t size; + if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) { + Logger::error("sysctl hw.drivestats failed"); + return; + } + num_drives = size / sizeof(struct io_sysctl); + + auto drives = std::unique_ptr { + reinterpret_cast(malloc(size)), + free + }; + + if (sysctl(mib, 3, drives.get(), &size, NULL, 0) == -1) { + Logger::error("sysctl hw.iostats failed"); + } + for (int i = 0; i < num_drives; i++) { + for (auto& [ignored, disk] : disks) { + if (disk.dev.string().find(drives[i].name) != string::npos) { + string mountpoint = mapping.at(disk.dev); + total_bytes_read = drives[i].rbytes; + total_bytes_write = drives[i].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_UVMEXP2}; + struct uvmexp_sysctl uvmexp; + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) == -1) { + Logger::error("uvmexp sysctl failed"); + bzero(&uvmexp, sizeof(uvmexp)); + } + memActive = (uvmexp.active + uvmexp.wired) * Shared::pageSize; + memWire = uvmexp.wired; + // freeMem = uvmexp.free * Shared::pageSize; + cachedMem = (uvmexp.filepages + uvmexp.execpages) * 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) { + uint64_t total = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_total") = total; + uint64_t swapped = uvmexp.swpginuse * 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) { + 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; + // 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 statvfs *stvfs; + int count = getmntinfo(&stvfs, MNT_WAIT); + vector found; + found.reserve(last_found.size()); + for (int i = 0; i < count; i++) { + auto fstype = string(stvfs[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 = stvfs[i].f_mntonname; + string dev = stvfs[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 = stvfs[i].f_bfree; + disks.at(mountpoint).total = stvfs[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 { + std::unordered_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", {}}}; + 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) + } + + 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) { + 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++; + } + } + + 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; + std::unordered_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 & LSRUN) return "Running"; + if (s & LSSLEEP) 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_proc2* kprocs = kvm_getproc2(kd(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count); + + for (int i = 0; i < count; i++) { + const struct kinfo_proc2* 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_getargv2(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 From 9831fef7abd8e7700cd8041891acbdb53e832ecb Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Tue, 20 Feb 2024 07:50:35 +0100 Subject: [PATCH 02/15] Add build files for NetBSD. --- .github/workflows/continuous-build-netbsd.yml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 .github/workflows/continuous-build-netbsd.yml diff --git a/.github/workflows/continuous-build-netbsd.yml b/.github/workflows/continuous-build-netbsd.yml new file mode 100755 index 0000000..b725b72 --- /dev/null +++ b/.github/workflows/continuous-build-netbsd.yml @@ -0,0 +1,66 @@ +name: Continuous Build NetBSD + +on: + workflow_dispatch: + push: + branches: + - main + tags-ignore: + - '*.*' + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - '!src/openbsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-netbsd.yml' + pull_request: + branches: + - main + paths: + - 'src/**' + - '!src/linux/**' + - '!src/osx/**' + - '!src/freebsd/**' + - '!src/openbsd/**' + - 'include/**' + - 'Makefile' + - '.github/workflows/continuous-build-netbsd.yml' + +jobs: + build-netbsd: + runs-on: ubuntu-22.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Compile + uses: vmactions/netbsd-vm@v1 + + with: + release: '9.3' + usesh: true + prepare: | + PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" + PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages" + PKG_PATH="$PKG_PATH/NetBSD/amd64/9.3/All/" + export PATH PKG_PATH + pkg_add pkgin + pkgin install -y gmake gcc-10 coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop + run: | + gmake CXX=/usr/pkg/gcc10/bin/g++ STATIC=true STRIP=true + GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") + mv bin/btop bin/btop-GCC10-"$GIT_HASH" + ls -alh bin + + - uses: actions/upload-artifact@v3 + with: + name: btop-x86_64-netbsd-9.3 + path: 'bin/*' + if-no-files-found: error + From 7a80b8eee828b9caf9db9479e2682dc39d2d06f0 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 22 Feb 2024 15:50:02 +0100 Subject: [PATCH 03/15] Fix some of the memory stats. --- src/netbsd/btop_collect.cpp | 41 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 6296b9b..69c125c 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -118,6 +118,7 @@ namespace Shared { long pageSize, clkTck, coreCount, physicalCoreCount, arg_max; int totalMem_len, kfscale; long bootTime; + size_t size; void init() { //? Shared global variables init @@ -132,8 +133,8 @@ namespace Shared { coreCount = ncpu; } - pageSize = sysconf(_SC_PAGE_SIZE); - if (pageSize <= 0) { + size = sizeof(pageSize); + if (sysctlbyname("hw.pagesize", &pageSize, &size, nullptr, 0) < 0) { pageSize = 4096; Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect."); } @@ -144,12 +145,10 @@ namespace Shared { 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) { + size = sizeof(totalMem); + if (sysctlbyname("hw.physmem", &totalMem, &size, nullptr, 0) < 0) { Logger::warning("Could not get memory size"); } - totalMem = memsize; struct timeval result; size = sizeof(result); @@ -599,9 +598,9 @@ namespace Mem { auto &mem = current_mem; static bool snapped = (getenv("BTOP_SNAPPED") != nullptr); - u_int memActive, memWire, cachedMem; - // u_int freeMem; + uint64_t memActive, memWired, memCached, memFree, memInactive; size_t size; + static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; struct uvmexp_sysctl uvmexp; size = sizeof(uvmexp); @@ -609,21 +608,21 @@ namespace Mem { Logger::error("uvmexp sysctl failed"); bzero(&uvmexp, sizeof(uvmexp)); } - memActive = (uvmexp.active + uvmexp.wired) * Shared::pageSize; - memWire = uvmexp.wired; - // freeMem = uvmexp.free * Shared::pageSize; - cachedMem = (uvmexp.filepages + uvmexp.execpages) * 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; + + memActive = (uvmexp.active + uvmexp.bootpages) * Shared::pageSize; + memWired = uvmexp.wired * Shared::pageSize; + memInactive = uvmexp.inactive * Shared::pageSize; + memFree = uvmexp.free * Shared::pageSize; + memCached = (uvmexp.filepages + uvmexp.execpages + uvmexp.anonpages) * Shared::pageSize; + mem.stats.at("used") = memActive + memWired; + mem.stats.at("available") = memInactive + memCached; + mem.stats.at("cached") = memCached; + mem.stats.at("free") = memFree; if (show_swap) { - uint64_t total = uvmexp.swpages * Shared::pageSize; - mem.stats.at("swap_total") = total; - uint64_t swapped = uvmexp.swpginuse * Shared::pageSize; - mem.stats.at("swap_used") = swapped; - mem.stats.at("swap_free") = total - swapped; + mem.stats.at("swap_total") = uvmexp.swpages * Shared::pageSize; + mem.stats.at("swap_used") = uvmexp.swpginuse * Shared::pageSize; + mem.stats.at("swap_free") = (uvmexp.swpages - uvmexp.swpginuse) * Shared::pageSize; } if (show_swap and mem.stats.at("swap_total") > 0) { From 00796eef5d80a741ee4dc7a2f8f61461b19c672a Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 22 Feb 2024 17:54:07 +0100 Subject: [PATCH 04/15] Attempt to fix the CI. --- .github/workflows/continuous-build-netbsd.yml | 9 ++++----- src/netbsd/btop_collect.cpp | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/continuous-build-netbsd.yml b/.github/workflows/continuous-build-netbsd.yml index b725b72..30fbb97 100755 --- a/.github/workflows/continuous-build-netbsd.yml +++ b/.github/workflows/continuous-build-netbsd.yml @@ -46,14 +46,13 @@ jobs: usesh: true prepare: | PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" - PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages" - PKG_PATH="$PKG_PATH/NetBSD/amd64/9.3/All/" + PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/9.3/All/" export PATH PKG_PATH - pkg_add pkgin - pkgin install -y gmake gcc-10 coreutils git + /usr/sbin/pkg_add pkgin + pkgin -y install gmake gcc10 coreutils git git config --global --add safe.directory /home/runner/work/btop/btop run: | - gmake CXX=/usr/pkg/gcc10/bin/g++ STATIC=true STRIP=true + gmake CXX=/usr/pkg/gcc10/bin/g++ CXXFLAGS='-DNDEBUG -I/usr/pkg/gcc10/include -I/usr/include -I/usr/pkg/include' LDFLAGS=' -L/usr/pkg/gcc10/lib -R/usr/pkg/gcc10/lib -L/usr/pkg/gcc10/lib/gcc/x86_64--netbsd/10.5.0 -Wl,-R/usr/pkg/gcc10/lib/gcc/x86_64--netbsd/10.5.0 -Wl,-zrelro -L/usr/lib -Wl,-R/usr/lib -L/usr/pkg/lib -Wl,-R/usr/pkg/lib' STATIC=true STRIP=true GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") mv bin/btop bin/btop-GCC10-"$GIT_HASH" ls -alh bin diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 69c125c..c18abe2 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -598,7 +598,7 @@ namespace Mem { auto &mem = current_mem; static bool snapped = (getenv("BTOP_SNAPPED") != nullptr); - uint64_t memActive, memWired, memCached, memFree, memInactive; + uint64_t memActive, memWired, memCached, memFree; size_t size; static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; @@ -609,13 +609,12 @@ namespace Mem { bzero(&uvmexp, sizeof(uvmexp)); } - memActive = (uvmexp.active + uvmexp.bootpages) * Shared::pageSize; + memActive = uvmexp.active * Shared::pageSize; memWired = uvmexp.wired * Shared::pageSize; - memInactive = uvmexp.inactive * Shared::pageSize; memFree = uvmexp.free * Shared::pageSize; memCached = (uvmexp.filepages + uvmexp.execpages + uvmexp.anonpages) * Shared::pageSize; mem.stats.at("used") = memActive + memWired; - mem.stats.at("available") = memInactive + memCached; + mem.stats.at("available") = Shared::totalMem - (memActive + memWired); mem.stats.at("cached") = memCached; mem.stats.at("free") = memFree; From c98673f94bf8930c62f3436515dbba30545f9d56 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Sat, 24 Feb 2024 14:33:41 +0100 Subject: [PATCH 05/15] Add CMake build support for NetBSD. --- .github/workflows/cmake-netbsd.yml | 45 ++++++++++++++++++++++++++++++ CMakeLists.txt | 8 ++++++ 2 files changed, 53 insertions(+) create mode 100755 .github/workflows/cmake-netbsd.yml diff --git a/.github/workflows/cmake-netbsd.yml b/.github/workflows/cmake-netbsd.yml new file mode 100755 index 0000000..088a1b1 --- /dev/null +++ b/.github/workflows/cmake-netbsd.yml @@ -0,0 +1,45 @@ +name: NetBSD CMake + +on: + push: + branches: main + tags-ignore: '*.*' + paths: + - '.github/workflows/cmake-netbsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/netbsd/*pp' + pull_request: + branches: main + paths: + - '.github/workflows/cmake-netbsd.yml' + - 'CMakeLists.txt' + - 'include/**' + - 'src/*pp' + - 'src/netbsd/*pp' + +jobs: + cmake_build_on_netbsd: + 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/netbsd-vm@v1 + with: + release: '9.3' + usesh: true + prepare: | + PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" + PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/9.3/All/" + export PATH PKG_PATH + /usr/sbin/pkg_add pkgin + pkgin -y install cmake ninja-build gcc10 coreutils git + git config --global --add safe.directory /home/runner/work/btop/btop + run: | + cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -DCMAKE_CXX_FLAGS="--std=c++20 -DNDEBUG -I/usr/pkg/gcc10/include -I/usr/include -I/usr/pkg/include" -B build -G Ninja -DBTOP_STATIC=ON + cmake --build build --verbose diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a4dbec..aab6771 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,8 @@ 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(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + target_sources(btop PRIVATE src/netbsd/btop_collect.cpp) elseif(LINUX) target_sources(btop PRIVATE src/linux/btop_collect.cpp) else() @@ -208,6 +210,12 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") endif() find_package(kvm REQUIRED) target_link_libraries(btop kvm::kvm) +elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + 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() From 78279bdb308e250a87ea9dac3db39e92f133c225 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Mon, 26 Feb 2024 04:44:27 +0100 Subject: [PATCH 06/15] Get the 64 bit value of physmem. --- src/netbsd/btop_collect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index c18abe2..3d426ab 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -146,7 +146,7 @@ namespace Shared { } size = sizeof(totalMem); - if (sysctlbyname("hw.physmem", &totalMem, &size, nullptr, 0) < 0) { + if (sysctlbyname("hw.physmem64", &totalMem, &size, nullptr, 0) < 0) { Logger::warning("Could not get memory size"); } From f9daa22d12b38ccef3c104c6946b5421b67dcdae Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 29 Feb 2024 20:12:25 +0100 Subject: [PATCH 07/15] Add support for reading battery stats. --- .github/workflows/cmake-netbsd.yml | 2 +- CMakeLists.txt | 5 +- Makefile | 2 +- cmake/Modules/Findproplib.cmake | 23 ++++ src/netbsd/btop_collect.cpp | 170 ++++++++++++++++++----------- 5 files changed, 135 insertions(+), 67 deletions(-) create mode 100644 cmake/Modules/Findproplib.cmake diff --git a/.github/workflows/cmake-netbsd.yml b/.github/workflows/cmake-netbsd.yml index 088a1b1..d4348aa 100755 --- a/.github/workflows/cmake-netbsd.yml +++ b/.github/workflows/cmake-netbsd.yml @@ -41,5 +41,5 @@ jobs: pkgin -y install cmake ninja-build gcc10 coreutils git git config --global --add safe.directory /home/runner/work/btop/btop run: | - cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -DCMAKE_CXX_FLAGS="--std=c++20 -DNDEBUG -I/usr/pkg/gcc10/include -I/usr/include -I/usr/pkg/include" -B build -G Ninja -DBTOP_STATIC=ON + cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -B build -G Ninja -DBTOP_STATIC=ON cmake --build build --verbose diff --git a/CMakeLists.txt b/CMakeLists.txt index aab6771..647fa12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,10 +212,11 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") target_link_libraries(btop kvm::kvm) elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(btop PRIVATE -static-libstdc++) + target_compile_options(btop PRIVATE -static-libstdc++ -std=c++20 -DNDEBUG) endif() find_package(kvm REQUIRED) - target_link_libraries(btop kvm::kvm) + find_package(proplib REQUIRED) + target_link_libraries(btop kvm::kvm proplib::proplib) endif() diff --git a/Makefile b/Makefile index c1d4cec..a16a47d 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ else ifeq ($(PLATFORM_LC),openbsd) else ifeq ($(PLATFORM_LC),netbsd) PLATFORM_DIR := netbsd THREADS := $(shell sysctl -n hw.ncpu || echo 1) - override ADDFLAGS += -lkvm + override ADDFLAGS += -lkvm -lprop export MAKE = gmake SU_GROUP := wheel else diff --git a/cmake/Modules/Findproplib.cmake b/cmake/Modules/Findproplib.cmake new file mode 100644 index 0000000..4be4393 --- /dev/null +++ b/cmake/Modules/Findproplib.cmake @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Find proplib – property container object library +# + +if(BSD) + find_path(proplib_INCLUDE_DIR NAMES prop/proplib.h) + find_library(proplib_LIBRARY NAMES libprop prop) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(proplib REQUIRED_VARS proplib_LIBRARY proplib_INCLUDE_DIR) + + if(proplib_FOUND AND NOT TARGET proplib::proplib) + add_library(proplib::proplib UNKNOWN IMPORTED) + set_target_properties(proplib::proplib PROPERTIES + IMPORTED_LOCATION "${proplib_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${proplib_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(proplib_INCLUDE_DIR proplib_LIBRARY) +endif() + diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 3d426ab..c5c82fb 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -29,8 +29,10 @@ tab-size = 4 #include #include // for inet_ntop stuff #include +#include #include #include +#include #include #include #include @@ -260,40 +262,6 @@ namespace Cpu { return name; } - int64_t get_sensor(string device, 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")) { @@ -387,36 +355,112 @@ namespace Cpu { auto get_battery() -> tuple { if (not has_battery) return {0, 0.0, 0, ""}; - long seconds = -1; - uint32_t percent = -1; - string status = "discharging"; - int64_t full, remaining; - full = get_sensor("acpibat0", 0); - remaining = get_sensor("acpibat0", 3); - int64_t state = get_sensor("acpibat0", 0); - if (full < 0) { + prop_dictionary_t dict, fields, props; + + int64_t totalCharge = 0; + int64_t totalCapacity = 0; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); 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"; + return {0, 0.0, 0, ""}; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); } - switch (state) { - case 0: - status = "full"; - percent = 100; - break; - case 2: - status = "charging"; - break; + has_battery = false; + Logger::warning("failed to open envsys dict"); + return {0, 0.0, 0, ""}; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + has_battery = false; + Logger::warning("no drivers registered for envsys"); + return {0, 0.0, 0, ""}; + } + + prop_object_t fieldsArray = prop_dictionary_get(prop_dictionary_t(dict), "acpibat0"); + if (prop_object_type(fieldsArray) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + has_battery = false; + Logger::warning("unknown device 'acpibat0'"); + return {0, 0.0, 0, ""}; + } + + prop_object_iterator_t fieldsIter = prop_array_iterator(prop_array_t(fieldsArray)); + if (fieldsIter == NULL) { + if (fd != -1) { + close(fd); + } + has_battery = false; + return {0, 0.0, 0, ""}; + } + + /* only assume battery is not present if explicitly stated */ + bool isBattery = false; + int64_t isPresent = 1; + int64_t curCharge = 0; + int64_t maxCharge = 0; + string status = "unknown"; + string prop_description = "no description"; + + while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fieldsIter))) != NULL) { + props = (prop_dictionary_t) prop_dictionary_get(fields, "device-properties"); + if (props != NULL) continue; + + prop_object_t curValue = prop_dictionary_get(fields, "cur-value"); + prop_object_t maxValue = prop_dictionary_get(fields, "max-value"); + prop_object_t description = prop_dictionary_get(fields, "description"); + + if (description == NULL || curValue == NULL) { + continue; + } + + + prop_description = prop_string_cstring(prop_string_t(description)); + + if (prop_description == "charge") { + if (maxValue == NULL) { + continue; + } + curCharge = prop_number_integer_value(prop_number_t(curValue)); + maxCharge = prop_number_integer_value(prop_number_t(maxValue)); + } + + if (prop_description == "present") { + isPresent = prop_number_integer_value(prop_number_t(curValue)); + } + + if (prop_description == "charging") { + status = prop_description; + string charging_type = prop_string_cstring(prop_string_t(prop_dictionary_get(fields, "type"))); + isBattery = charging_type == "Battery charge" ? true : false; + } + + if (isBattery && isPresent) { + totalCharge += curCharge; + totalCapacity += maxCharge; } } - return {percent, 0.0, seconds, status}; + prop_object_iterator_release(fieldsIter); + prop_object_release(dict); + + uint32_t percent = ((double)totalCharge / (double)totalCapacity) * 100.0; + + if (percent == 100) { + status = "full"; + } + + return {percent, -1, -1, status}; } auto collect(bool no_update) -> cpu_info & { @@ -562,7 +606,7 @@ namespace Mem { size_t size; if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) { - Logger::error("sysctl hw.drivestats failed"); + Logger::error("sysctl hw.drivestats failed"); return; } num_drives = size / sizeof(struct io_sysctl); @@ -573,12 +617,12 @@ namespace Mem { }; if (sysctl(mib, 3, drives.get(), &size, NULL, 0) == -1) { - Logger::error("sysctl hw.iostats failed"); + Logger::error("sysctl hw.iostats failed"); } for (int i = 0; i < num_drives; i++) { for (auto& [ignored, disk] : disks) { if (disk.dev.string().find(drives[i].name) != string::npos) { - string mountpoint = mapping.at(disk.dev); + string mountpoint = mapping.at(disk.dev); total_bytes_read = drives[i].rbytes; total_bytes_write = drives[i].wbytes; assign_values(disk, total_bytes_read, total_bytes_write); @@ -771,7 +815,7 @@ namespace Net { class getifaddr_wrapper { struct ifaddrs *ifaddr; - public: + public: int status; getifaddr_wrapper() { status = getifaddrs(&ifaddr); } ~getifaddr_wrapper() { freeifaddrs(ifaddr); } From ea0f453a14e7dbae8a96159f7815e99b7bba1ce2 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Fri, 1 Mar 2024 13:27:56 +0100 Subject: [PATCH 08/15] Add support for reading thermal stats. --- src/netbsd/btop_collect.cpp | 217 +++++++++++++++++++++++++++--------- 1 file changed, 164 insertions(+), 53 deletions(-) diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index c5c82fb..ad01367 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -1,4 +1,5 @@ /* Copyright 2021 Aristocratos (jakob@qvantnet.com) + Copyright 2024 Santhosh Raju (fox@NetBSD.org) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -42,13 +43,10 @@ tab-size = 4 #include #include #include -//#include #include #include #include #include -//#include -//#include #include #include #include @@ -264,38 +262,151 @@ namespace Cpu { 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"); -// } -// } + prop_dictionary_t dict; + prop_object_t fields_array; + // List of common thermal sensors in NetBSD. + const string sensors[6] = { + "acpitz0", + "acpitz1", + "coretemp0", + "coretemp1", + "thinkpad0", + "amdzentemp0" + }; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); + return got_sensors; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("failed to open envsys dict"); + return got_sensors; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("no drivers registered for envsys"); + return got_sensors; + } + + // Search through a known list of sensors and break the loop on finding the first. + for(const string &sensor : sensors) { + fields_array = prop_dictionary_get(prop_dictionary_t(dict), sensor.c_str()); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + Logger::warning("unknown device " + sensor); + } else { + Cpu::cpu_sensor = sensor; + break; + } + } + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + return got_sensors; + } + + if (Config::getB("show_coretemp") and Config::getB("check_temp")) { + got_sensors = true; + } 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"), 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(); -// } + int64_t current_temp = -1; + current_cpu.temp_max = 95; + prop_dictionary_t dict, fields, props; + + int fd = open(_PATH_SYSMON, O_RDONLY); + if (fd == -1) { + Logger::warning("failed to open " + string(_PATH_SYSMON)); + return; + } + + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("failed to open envsys dict"); + return; + } + + if (prop_dictionary_count(dict) == 0) { + if (fd != -1) { + close(fd); + } + Logger::warning("no drivers registered for envsys"); + return; + } + + prop_object_t fields_array = prop_dictionary_get(prop_dictionary_t(dict), Cpu::cpu_sensor.c_str()); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { + if (fd != -1) { + close(fd); + } + Logger::warning("unknown device " + Cpu::cpu_sensor); + return; + } + + prop_object_iterator_t fields_iter = prop_array_iterator(prop_array_t(fields_array)); + if (fields_iter == NULL) { + if (fd != -1) { + close(fd); + } + return; + } + + string prop_description = "no description"; + while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fields_iter))) != NULL) { + props = (prop_dictionary_t) prop_dictionary_get(fields, "device-properties"); + if (props != NULL) continue; + + prop_object_t cur_value = prop_dictionary_get(fields, "cur-value"); + prop_object_t max_value = prop_dictionary_get(fields, "critical-max"); + prop_object_t description = prop_dictionary_get(fields, "description"); + + if (description == NULL || cur_value == NULL) { + continue; + } + + + prop_description = prop_string_cstring(prop_string_t(description)); + + if (prop_description == "temperature") { + current_temp = prop_number_integer_value(prop_number_t(cur_value)); + if (max_value != NULL) { + current_cpu.temp_max = MUKTOC(prop_number_integer_value(prop_number_t(max_value))); + } + } + } + + prop_object_iterator_release(fields_iter); + prop_object_release(dict); + + if (current_temp > -1) { + current_temp = MUKTOC(current_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(current_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(current_temp); + if (current_cpu.temp.at(0).size() > 20) { + current_cpu.temp.at(0).pop_front(); + } + } } @@ -357,8 +468,8 @@ namespace Cpu { prop_dictionary_t dict, fields, props; - int64_t totalCharge = 0; - int64_t totalCapacity = 0; + int64_t total_charge = 0; + int64_t total_capacity = 0; int fd = open(_PATH_SYSMON, O_RDONLY); if (fd == -1) { @@ -385,8 +496,8 @@ namespace Cpu { return {0, 0.0, 0, ""}; } - prop_object_t fieldsArray = prop_dictionary_get(prop_dictionary_t(dict), "acpibat0"); - if (prop_object_type(fieldsArray) != PROP_TYPE_ARRAY) { + prop_object_t fields_array = prop_dictionary_get(prop_dictionary_t(dict), "acpibat0"); + if (prop_object_type(fields_array) != PROP_TYPE_ARRAY) { if (fd != -1) { close(fd); } @@ -395,8 +506,8 @@ namespace Cpu { return {0, 0.0, 0, ""}; } - prop_object_iterator_t fieldsIter = prop_array_iterator(prop_array_t(fieldsArray)); - if (fieldsIter == NULL) { + prop_object_iterator_t fields_iter = prop_array_iterator(prop_array_t(fields_array)); + if (fields_iter == NULL) { if (fd != -1) { close(fd); } @@ -405,22 +516,22 @@ namespace Cpu { } /* only assume battery is not present if explicitly stated */ - bool isBattery = false; - int64_t isPresent = 1; - int64_t curCharge = 0; - int64_t maxCharge = 0; + bool is_battery = false; + int64_t is_present = 1; + int64_t cur_charge = 0; + int64_t max_charge = 0; string status = "unknown"; string prop_description = "no description"; - while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fieldsIter))) != NULL) { + while ((fields = (prop_dictionary_t) prop_object_iterator_next(prop_object_iterator_t(fields_iter))) != NULL) { props = (prop_dictionary_t) prop_dictionary_get(fields, "device-properties"); if (props != NULL) continue; - prop_object_t curValue = prop_dictionary_get(fields, "cur-value"); - prop_object_t maxValue = prop_dictionary_get(fields, "max-value"); + prop_object_t cur_value = prop_dictionary_get(fields, "cur-value"); + prop_object_t max_value = prop_dictionary_get(fields, "max-value"); prop_object_t description = prop_dictionary_get(fields, "description"); - if (description == NULL || curValue == NULL) { + if (description == NULL || cur_value == NULL) { continue; } @@ -428,33 +539,33 @@ namespace Cpu { prop_description = prop_string_cstring(prop_string_t(description)); if (prop_description == "charge") { - if (maxValue == NULL) { + if (max_value == NULL) { continue; } - curCharge = prop_number_integer_value(prop_number_t(curValue)); - maxCharge = prop_number_integer_value(prop_number_t(maxValue)); + cur_charge = prop_number_integer_value(prop_number_t(cur_value)); + max_charge = prop_number_integer_value(prop_number_t(max_value)); } if (prop_description == "present") { - isPresent = prop_number_integer_value(prop_number_t(curValue)); + is_present = prop_number_integer_value(prop_number_t(cur_value)); } if (prop_description == "charging") { status = prop_description; string charging_type = prop_string_cstring(prop_string_t(prop_dictionary_get(fields, "type"))); - isBattery = charging_type == "Battery charge" ? true : false; + is_battery = charging_type == "Battery charge" ? true : false; } - if (isBattery && isPresent) { - totalCharge += curCharge; - totalCapacity += maxCharge; + if (is_battery && is_present) { + total_charge += cur_charge; + total_capacity += max_charge; } } - prop_object_iterator_release(fieldsIter); + prop_object_iterator_release(fields_iter); prop_object_release(dict); - uint32_t percent = ((double)totalCharge / (double)totalCapacity) * 100.0; + uint32_t percent = ((double)total_charge / (double)total_capacity) * 100.0; if (percent == 100) { status = "full"; From b472dce4091204d4366dc9ed2ab8e2ddd1591ff4 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Sun, 3 Mar 2024 10:27:37 +0100 Subject: [PATCH 09/15] Simplify the CI command. --- .github/workflows/continuous-build-netbsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-build-netbsd.yml b/.github/workflows/continuous-build-netbsd.yml index 30fbb97..d1a20ea 100755 --- a/.github/workflows/continuous-build-netbsd.yml +++ b/.github/workflows/continuous-build-netbsd.yml @@ -52,7 +52,7 @@ jobs: pkgin -y install gmake gcc10 coreutils git git config --global --add safe.directory /home/runner/work/btop/btop run: | - gmake CXX=/usr/pkg/gcc10/bin/g++ CXXFLAGS='-DNDEBUG -I/usr/pkg/gcc10/include -I/usr/include -I/usr/pkg/include' LDFLAGS=' -L/usr/pkg/gcc10/lib -R/usr/pkg/gcc10/lib -L/usr/pkg/gcc10/lib/gcc/x86_64--netbsd/10.5.0 -Wl,-R/usr/pkg/gcc10/lib/gcc/x86_64--netbsd/10.5.0 -Wl,-zrelro -L/usr/lib -Wl,-R/usr/lib -L/usr/pkg/lib -Wl,-R/usr/pkg/lib' STATIC=true STRIP=true + gmake CXX=/usr/pkg/gcc10/bin/g++ CXXFLAGS='-DNDEBUG -I/usr/pkg/gcc10/include -I/usr/include -I/usr/pkg/include' STATIC=true STRIP=true GIT_HASH=$(git rev-parse --short "$GITHUB_SHA") mv bin/btop bin/btop-GCC10-"$GIT_HASH" ls -alh bin From b76d5e3f553bd3d80154897c61001f80a0ab4255 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Sun, 3 Mar 2024 13:09:17 +0100 Subject: [PATCH 10/15] Use kvm and ifaddr defs from shared.hpp. --- src/btop_shared.hpp | 4 ++-- src/netbsd/btop_collect.cpp | 37 +++++++------------------------------ 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 0a8f452..f1f974e 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -34,7 +34,7 @@ tab-size = 4 #include // clang-format on -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) # include #endif @@ -93,7 +93,7 @@ namespace Shared { extern long coreCount, page_size, clk_tck; -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) struct KvmDeleter { void operator()(kvm_t* handle) { kvm_close(handle); diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index ad01367..ddbe3db 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -183,18 +183,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 { @@ -922,17 +910,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"); @@ -942,10 +919,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; } @@ -957,7 +934,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; @@ -1244,8 +1221,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_proc2* kprocs = kvm_getproc2(kd(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count); + Shared::KvmPtr kd {kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, buf)}; + const struct kinfo_proc2* kprocs = kvm_getproc2(kd.get(), KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count); for (int i = 0; i < count; i++) { const struct kinfo_proc2* kproc = &kprocs[i]; @@ -1272,7 +1249,7 @@ namespace Proc { continue; } new_proc.name = kproc->p_comm; - char** argv = kvm_getargv2(kd(), kproc, 0); + char** argv = kvm_getargv2(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 d03c36b17381a344579a7ce63b0fff45a22bceb3 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Sun, 3 Mar 2024 13:15:59 +0100 Subject: [PATCH 11/15] Ignore netbsd folder in other builds. --- .github/workflows/continuous-build-freebsd.yml | 2 ++ .github/workflows/continuous-build-gpu.yml | 2 ++ .github/workflows/continuous-build-linux.yml | 2 ++ .github/workflows/continuous-build-macos.yml | 2 ++ .github/workflows/continuous-build-openbsd.yml | 2 ++ .github/workflows/test-snap-can-build.yml | 4 ++++ 6 files changed, 14 insertions(+) diff --git a/.github/workflows/continuous-build-freebsd.yml b/.github/workflows/continuous-build-freebsd.yml index 5521ff7..3d4f74f 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/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' @@ -22,6 +23,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/osx/**' + - '!src/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' diff --git a/.github/workflows/continuous-build-gpu.yml b/.github/workflows/continuous-build-gpu.yml index e3bdf01..34c3e11 100644 --- a/.github/workflows/continuous-build-gpu.yml +++ b/.github/workflows/continuous-build-gpu.yml @@ -11,6 +11,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' @@ -22,6 +23,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' diff --git a/.github/workflows/continuous-build-linux.yml b/.github/workflows/continuous-build-linux.yml index 39de640..c70019e 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/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' @@ -22,6 +23,7 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' diff --git a/.github/workflows/continuous-build-macos.yml b/.github/workflows/continuous-build-macos.yml index c8915dd..965e732 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/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' @@ -22,6 +23,7 @@ on: - 'src/**' - '!src/linux/**' - '!src/freebsd/**' + - '!src/netbsd/**' - '!src/openbsd/**' - 'include/**' - 'Makefile' diff --git a/.github/workflows/continuous-build-openbsd.yml b/.github/workflows/continuous-build-openbsd.yml index 6925ebb..5bf1dc0 100644 --- a/.github/workflows/continuous-build-openbsd.yml +++ b/.github/workflows/continuous-build-openbsd.yml @@ -12,6 +12,7 @@ on: - '!src/linux/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-openbsd.yml' @@ -23,6 +24,7 @@ on: - '!src/linux/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/continuous-build-openbsd.yml' diff --git a/.github/workflows/test-snap-can-build.yml b/.github/workflows/test-snap-can-build.yml index 6df8e47..8726a9e 100644 --- a/.github/workflows/test-snap-can-build.yml +++ b/.github/workflows/test-snap-can-build.yml @@ -9,6 +9,8 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/test-snap-can-build.yml' @@ -18,6 +20,8 @@ on: - 'src/**' - '!src/osx/**' - '!src/freebsd/**' + - '!src/netbsd/**' + - '!src/openbsd/**' - 'include/**' - 'Makefile' - '.github/workflows/test-snap-can-build.yml' From 9b499c1daec03ca12e42c923931960765c350b64 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 2 May 2024 06:37:41 +0200 Subject: [PATCH 12/15] Update README.md with NetBSD details. --- README.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/README.md b/README.md index fdd4c31..3e51a5a 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) +![NetBSD](https://img.shields.io/badge/-NetBSD-black?logo=netbsd) ![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) @@ -18,6 +19,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 NetBSD](https://github.com/aristocratos/btop/actions/workflows/continuous-build-netbsd.yml/badge.svg)](https://github.com/aristocratos/btop/actions/workflows/continuous-build-netbsd.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 @@ -35,6 +37,7 @@ * [Compilation Linux](#compilation-linux) * [Compilation macOS](#compilation-macos-osx) * [Compilation FreeBSD](#compilation-freebsd) +* [Compilation NetBSD](#compilation-netbsd) * [Compilation OpenBSD](#compilation-openbsd) * [GPU compatibility](#gpu-compatibility) * [Installing the snap](#installing-the-snap) @@ -343,6 +346,10 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa ```sh pkg install btop ``` +* **NetBSD** + ```sh + pkg_add install btop + ``` **Binary release on Homebrew (macOS (x86_64 & ARM64) / Linux (x86_64))** @@ -902,6 +909,168 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa +## Compilation NetBSD + + Requires at least GCC 10. + + Note that GNU make (`gmake`) is required to compile on NetBSD. + +
+ + +### With gmake + + +1. **Install dependencies** + + ```bash + pkg_add install gmake gcc10 coreutils git + ``` + +2. **Clone repository** + + ```bash + git clone https://github.com/aristocratos/btop.git + cd btop + ``` + +3. **Compile** + + ```bash + gmake CXXFLAGS="-DNDEBUG" + ``` + + 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 | + | `FORTIFY_SOURCE=false` | Disable fortification with `_FORTIFY_SOURCE=3` | + | `ADDFLAGS=` | For appending flags to both compiler and linker | + | `CXX=` | Manually 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 + ``` + +
+
+ + +### With CMake (Community maintained) + + +1. **Install build dependencies** + + Requires GCC, CMake, Ninja and Git + + ```bash + pkg_add install cmake ninja-build gcc10 coreutils git + ``` + +2. **Clone the repository** + + ```bash + git clone https://github.com/aristocratos/btop.git && cd btop + ``` + +3. **Compile** + + ```bash + # Configure + cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -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_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 + ``` + +
+ ## Compilation OpenBSD Requires at least GCC 10. From 25ddc5d4bd107130296890aa6d8c9791e980c3a0 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 2 May 2024 06:39:55 +0200 Subject: [PATCH 13/15] Bump NetBSD to 10.0 in CI. --- .github/workflows/cmake-netbsd.yml | 4 ++-- .github/workflows/continuous-build-netbsd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) mode change 100755 => 100644 .github/workflows/cmake-netbsd.yml mode change 100755 => 100644 .github/workflows/continuous-build-netbsd.yml diff --git a/.github/workflows/cmake-netbsd.yml b/.github/workflows/cmake-netbsd.yml old mode 100755 new mode 100644 index d4348aa..175f4f8 --- a/.github/workflows/cmake-netbsd.yml +++ b/.github/workflows/cmake-netbsd.yml @@ -31,11 +31,11 @@ jobs: - name: Compile uses: vmactions/netbsd-vm@v1 with: - release: '9.3' + release: '10.0' usesh: true prepare: | PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" - PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/9.3/All/" + PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.0/All/" export PATH PKG_PATH /usr/sbin/pkg_add pkgin pkgin -y install cmake ninja-build gcc10 coreutils git diff --git a/.github/workflows/continuous-build-netbsd.yml b/.github/workflows/continuous-build-netbsd.yml old mode 100755 new mode 100644 index d1a20ea..6bc2ad7 --- a/.github/workflows/continuous-build-netbsd.yml +++ b/.github/workflows/continuous-build-netbsd.yml @@ -42,11 +42,11 @@ jobs: uses: vmactions/netbsd-vm@v1 with: - release: '9.3' + release: '10.0' usesh: true prepare: | PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" - PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/9.3/All/" + PKG_PATH="https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.0/All/" export PATH PKG_PATH /usr/sbin/pkg_add pkgin pkgin -y install gmake gcc10 coreutils git From d75a24145deaf9b871e72634baf75ccef2161c5b Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 2 May 2024 06:47:46 +0200 Subject: [PATCH 14/15] Fix the pkg_add command in README. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e51a5a..4de2e84 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa ``` * **NetBSD** ```sh - pkg_add install btop + pkg_add btop ``` @@ -924,7 +924,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 install gmake gcc10 coreutils git + pkg_add gmake gcc10 coreutils git ``` 2. **Clone repository** @@ -1014,7 +1014,7 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa Requires GCC, CMake, Ninja and Git ```bash - pkg_add install cmake ninja-build gcc10 coreutils git + pkg_add cmake ninja-build gcc10 coreutils git ``` 2. **Clone the repository** From 93c0f925fe47ccac44e412b2456c09addc8e5dd4 Mon Sep 17 00:00:00 2001 From: Santhosh Raju Date: Thu, 2 May 2024 06:58:40 +0200 Subject: [PATCH 15/15] Disable STATIC build for cmake. --- .github/workflows/cmake-netbsd.yml | 2 +- README.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cmake-netbsd.yml b/.github/workflows/cmake-netbsd.yml index 175f4f8..c3ec8d5 100644 --- a/.github/workflows/cmake-netbsd.yml +++ b/.github/workflows/cmake-netbsd.yml @@ -41,5 +41,5 @@ jobs: pkgin -y install cmake ninja-build gcc10 coreutils git git config --global --add safe.directory /home/runner/work/btop/btop run: | - cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -B build -G Ninja -DBTOP_STATIC=ON + cmake -DCMAKE_CXX_COMPILER="/usr/pkg/gcc10/bin/g++" -B build -G Ninja -DBTOP_STATIC=OFF cmake --build build --verbose diff --git a/README.md b/README.md index 4de2e84..8002a0a 100644 --- a/README.md +++ b/README.md @@ -1038,7 +1038,6 @@ If you have an AMD GPU `rocm_smi_lib` is required, which may or may not be packa | 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) |