diff --git a/include/factory.hpp b/include/factory.hpp index ff72749e..cb25078d 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -39,6 +39,7 @@ #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" #include "modules/cpu_frequency.hpp" +#include "modules/cpu_usage.hpp" #include "modules/load.hpp" #endif #include "modules/idle_inhibitor.hpp" diff --git a/include/modules/cpu_usage.hpp b/include/modules/cpu_usage.hpp new file mode 100644 index 00000000..0e57124a --- /dev/null +++ b/include/modules/cpu_usage.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "AButton.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class CpuUsage : public AButton { + public: + CpuUsage(const std::string&, const Json::Value&); + ~CpuUsage() = default; + auto update() -> void; + + private: + std::tuple, std::string> getCpuUsage(); + std::vector> parseCpuinfo(); + + std::vector> prev_times_; + + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index a20748d1..2a0922cb 100644 --- a/meson.build +++ b/meson.build @@ -192,6 +192,8 @@ if is_linux 'src/modules/cpu/linux.cpp', 'src/modules/cpu_frequency/common.cpp', 'src/modules/cpu_frequency/linux.cpp', + 'src/modules/cpu_usage/common.cpp', + 'src/modules/cpu_usage/linux.cpp', 'src/modules/memory/common.cpp', 'src/modules/memory/linux.cpp', ) @@ -201,6 +203,8 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd src_files += files( 'src/modules/cpu/bsd.cpp', 'src/modules/cpu/common.cpp', + 'src/modules/cpu_usage/bsd.cpp', + 'src/modules/cpu_usage/common.cpp', 'src/modules/memory/bsd.cpp', 'src/modules/memory/common.cpp', ) diff --git a/src/factory.cpp b/src/factory.cpp index b0b9af47..18b14427 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -102,6 +102,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "cpu_frequency") { return new waybar::modules::CpuFrequency(id, config_[name]); } + if (ref == "cpu_usage") { + return new waybar::modules::CpuUsage(id, config_[name]); + } if (ref == "load") { return new waybar::modules::Load(id, config_[name]); } diff --git a/src/modules/cpu_usage/bsd.cpp b/src/modules/cpu_usage/bsd.cpp new file mode 100644 index 00000000..c987a770 --- /dev/null +++ b/src/modules/cpu_usage/bsd.cpp @@ -0,0 +1,102 @@ +#include +// clang-format off +#include +#include +// clang-format on +#include // sysconf + +#include // NAN +#include // malloc + +#include "modules/cpu_usage.hpp" + +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#if defined(__NetBSD__) +typedef uint64_t cp_time_t; +#else +typedef long cp_time_t; +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) +typedef uint64_t pcp_time_t; +#else +typedef long pcp_time_t; +#endif + +std::vector> waybar::modules::CpuUsage::parseCpuinfo() { + cp_time_t sum_cp_time[CPUSTATES]; + size_t sum_sz = sizeof(sum_cp_time); + int ncpu = sysconf(_SC_NPROCESSORS_CONF); + size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t); + pcp_time_t *cp_time = static_cast(malloc(sz)), *pcp_time = cp_time; +#if defined(__NetBSD__) + int mib[] = { + CTL_KERN, + KERN_CP_TIME, + }; + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_time failed"); + } + for (int state = 0; state < CPUSTATES; state++) { + cp_time[state] = sum_cp_time[state]; + } + pcp_time += CPUSTATES; + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), pcp_time, &sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_time failed"); + } +#elif defined(__OpenBSD__) + { + int mib[] = { + CTL_KERN, + KERN_CPTIME, + }; + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_time failed"); + } + } + for (int state = 0; state < CPUSTATES; state++) { + cp_time[state] = sum_cp_time[state]; + } + pcp_time = cp_time; + sz /= ncpu + 1; + { + int mib[] = { + CTL_KERN, + KERN_CPTIME2, + 0, + }; + for (int cpu = 0; cpu < ncpu; cpu++) { + mib[2] = cpu; + pcp_time += CPUSTATES; + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), pcp_time, &sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_time2 failed"); + } + } + } +#else + if (sysctlbyname("kern.cp_time", sum_cp_time, &sum_sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_time failed"); + } + for (int state = 0; state < CPUSTATES; state++) { + cp_time[state] = sum_cp_time[state]; + } + pcp_time += CPUSTATES; + if (sysctlbyname("kern.cp_times", pcp_time, &sz, NULL, 0)) { + throw std::runtime_error("sysctl kern.cp_times failed"); + } +#endif + std::vector> cpuinfo; + for (int cpu = 0; cpu < ncpu + 1; cpu++) { + pcp_time_t total = 0, *single_cp_time = &cp_time[cpu * CPUSTATES]; + for (int state = 0; state < CPUSTATES; state++) { + total += single_cp_time[state]; + } + cpuinfo.emplace_back(single_cp_time[CP_IDLE], total); + } + free(cp_time); + return cpuinfo; +} diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp new file mode 100644 index 00000000..140373a0 --- /dev/null +++ b/src/modules/cpu_usage/common.cpp @@ -0,0 +1,78 @@ +#include "modules/cpu_usage.hpp" + +// In the 80000 version of fmt library authors decided to optimize imports +// and moved declarations required for fmt::dynamic_format_arg_store in new +// header fmt/args.h +#if (FMT_VERSION >= 80000) +#include +#else +#include +#endif + +waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& config) + : AButton(config, "cpu_usage", id, "{usage}%", 10) { + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; +} + +auto waybar::modules::CpuUsage::update() -> void { + // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both + auto [cpu_usage, tooltip] = getCpuUsage(); + if (tooltipEnabled()) { + button_.set_tooltip_text(tooltip); + } + auto format = format_; + auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; + auto state = getState(total_usage); + if (!state.empty() && config_["format-" + state].isString()) { + format = config_["format-" + state].asString(); + } + + if (format.empty()) { + event_box_.hide(); + } else { + event_box_.show(); + auto icons = std::vector{state}; + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("usage", total_usage)); + store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); + for (size_t i = 1; i < cpu_usage.size(); ++i) { + auto core_i = i - 1; + auto core_format = fmt::format("usage{}", core_i); + store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); + auto icon_format = fmt::format("icon{}", core_i); + store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + } + label_->set_markup(fmt::vformat(format, store)); + } + + // Call parent update + AButton::update(); +} + +std::tuple, std::string> waybar::modules::CpuUsage::getCpuUsage() { + if (prev_times_.empty()) { + prev_times_ = parseCpuinfo(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + std::vector> curr_times = parseCpuinfo(); + std::string tooltip; + std::vector usage; + for (size_t i = 0; i < curr_times.size(); ++i) { + auto [curr_idle, curr_total] = curr_times[i]; + auto [prev_idle, prev_total] = prev_times_[i]; + const float delta_idle = curr_idle - prev_idle; + const float delta_total = curr_total - prev_total; + uint16_t tmp = 100 * (1 - delta_idle / delta_total); + if (i == 0) { + tooltip = fmt::format("Total: {}%", tmp); + } else { + tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); + } + usage.push_back(tmp); + } + prev_times_ = curr_times; + return {usage, tooltip}; +} diff --git a/src/modules/cpu_usage/linux.cpp b/src/modules/cpu_usage/linux.cpp new file mode 100644 index 00000000..28930c1c --- /dev/null +++ b/src/modules/cpu_usage/linux.cpp @@ -0,0 +1,31 @@ +#include + +#include "modules/cpu_usage.hpp" + +std::vector> waybar::modules::CpuUsage::parseCpuinfo() { + const std::string data_dir_ = "/proc/stat"; + std::ifstream info(data_dir_); + if (!info.is_open()) { + throw std::runtime_error("Can't open " + data_dir_); + } + std::vector> cpuinfo; + std::string line; + while (getline(info, line)) { + if (line.substr(0, 3).compare("cpu") != 0) { + break; + } + std::stringstream sline(line.substr(5)); + std::vector times; + for (size_t time = 0; sline >> time; times.push_back(time)) + ; + + size_t idle_time = 0; + size_t total_time = 0; + if (times.size() >= 4) { + idle_time = times[3]; + total_time = std::accumulate(times.begin(), times.end(), 0); + } + cpuinfo.emplace_back(idle_time, total_time); + } + return cpuinfo; +}