2021-06-19 14:57:27 +02:00
|
|
|
/* 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 <fstream>
|
|
|
|
#include <ranges>
|
|
|
|
#include <cmath>
|
|
|
|
#include <unistd.h>
|
2021-07-29 23:40:56 +02:00
|
|
|
#include <numeric>
|
2021-08-10 20:20:33 +02:00
|
|
|
#include <sys/statvfs.h>
|
2021-08-15 23:20:55 +02:00
|
|
|
#include <netdb.h>
|
|
|
|
#include <ifaddrs.h>
|
|
|
|
#include <net/if.h>
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-10-17 01:45:26 +02:00
|
|
|
#if !(defined(STATIC_BUILD) && defined(__GLIBC__))
|
2021-10-06 10:47:24 +02:00
|
|
|
#include <pwd.h>
|
|
|
|
#endif
|
|
|
|
|
2021-06-20 00:49:13 +02:00
|
|
|
#include <btop_shared.hpp>
|
2021-06-19 22:48:31 +02:00
|
|
|
#include <btop_config.hpp>
|
|
|
|
#include <btop_tools.hpp>
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-09-01 21:40:13 +02:00
|
|
|
using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min;
|
|
|
|
using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater;
|
2021-06-19 14:57:27 +02:00
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace rng = std::ranges;
|
|
|
|
using namespace Tools;
|
|
|
|
|
|
|
|
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
namespace Cpu {
|
2021-08-03 23:47:46 +02:00
|
|
|
vector<long long> core_old_totals;
|
|
|
|
vector<long long> core_old_idles;
|
2021-07-29 23:40:56 +02:00
|
|
|
vector<string> available_fields;
|
2021-09-12 15:58:23 +02:00
|
|
|
vector<string> available_sensors = {"Auto"};
|
2021-07-29 23:40:56 +02:00
|
|
|
cpu_info current_cpu;
|
2021-08-03 23:47:46 +02:00
|
|
|
fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq";
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
unordered_flat_map<string, Sensor> found_sensors;
|
|
|
|
string cpu_sensor;
|
|
|
|
vector<string> core_sensors;
|
|
|
|
unordered_flat_map<int, int> core_mapping;
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
|
|
|
|
2021-09-01 21:40:13 +02:00
|
|
|
namespace Mem {
|
|
|
|
double old_uptime;
|
2021-08-22 16:04:01 +02:00
|
|
|
}
|
|
|
|
|
2021-06-25 23:58:19 +02:00
|
|
|
namespace Shared {
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
fs::path procPath, passwd_path;
|
2021-07-26 01:06:34 +02:00
|
|
|
long pageSize, clkTck, coreCount;
|
2021-06-25 23:58:19 +02:00
|
|
|
|
2021-07-21 03:17:34 +02:00
|
|
|
void init() {
|
2021-07-26 01:06:34 +02:00
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
//? Shared global variables init
|
|
|
|
procPath = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
|
|
|
|
if (procPath.empty())
|
2021-07-24 02:13:26 +02:00
|
|
|
throw std::runtime_error("Proc filesystem not found or no permission to read from it!");
|
2021-06-25 23:58:19 +02:00
|
|
|
|
2021-07-24 02:13:26 +02:00
|
|
|
passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : "";
|
|
|
|
if (passwd_path.empty())
|
|
|
|
Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
|
2021-06-25 23:58:19 +02:00
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
coreCount = sysconf(_SC_NPROCESSORS_ONLN);
|
|
|
|
if (coreCount < 1) {
|
|
|
|
coreCount = 1;
|
|
|
|
Logger::warning("Could not determine number of cores, defaulting to 1.");
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:06:34 +02:00
|
|
|
pageSize = sysconf(_SC_PAGE_SIZE);
|
|
|
|
if (pageSize <= 0) {
|
|
|
|
pageSize = 4096;
|
2021-06-25 23:58:19 +02:00
|
|
|
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:06:34 +02:00
|
|
|
clkTck = sysconf(_SC_CLK_TCK);
|
|
|
|
if (clkTck <= 0) {
|
|
|
|
clkTck = 100;
|
2021-07-21 03:17:34 +02:00
|
|
|
Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect.");
|
2021-06-25 23:58:19 +02:00
|
|
|
}
|
2021-10-06 18:06:05 +02:00
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
//? Init for namespace Cpu
|
2021-08-03 23:47:46 +02:00
|
|
|
if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear();
|
2021-07-29 23:40:56 +02:00
|
|
|
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()) Cpu::available_fields.push_back(field);
|
|
|
|
}
|
|
|
|
Cpu::cpuName = Cpu::get_cpuName();
|
2021-08-03 23:47:46 +02:00
|
|
|
Cpu::got_sensors = Cpu::get_sensors();
|
2021-09-12 15:58:23 +02:00
|
|
|
for (const auto& [sensor, ignored] : Cpu::found_sensors) {
|
|
|
|
Cpu::available_sensors.push_back(sensor);
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
Cpu::core_mapping = Cpu::get_core_mapping();
|
|
|
|
|
2021-08-10 20:20:33 +02:00
|
|
|
//? Init for namespace Mem
|
2021-09-01 21:40:13 +02:00
|
|
|
Mem::old_uptime = system_uptime();
|
2021-08-10 20:20:33 +02:00
|
|
|
Mem::collect();
|
2021-08-03 23:47:46 +02:00
|
|
|
|
2021-06-25 23:58:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-04 22:02:31 +02:00
|
|
|
namespace Cpu {
|
2021-07-29 23:40:56 +02:00
|
|
|
string cpuName;
|
|
|
|
string cpuHz;
|
2021-09-17 14:25:54 +02:00
|
|
|
bool has_battery = true;
|
|
|
|
tuple<int, long, string> current_bat;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
const array<string, 10> time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"};
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
unordered_flat_map<string, long long> cpu_old = {
|
2021-07-29 23:40:56 +02:00
|
|
|
{"totals", 0},
|
|
|
|
{"idles", 0},
|
|
|
|
{"user", 0},
|
|
|
|
{"nice", 0},
|
|
|
|
{"system", 0},
|
|
|
|
{"idle", 0},
|
|
|
|
{"iowait", 0},
|
|
|
|
{"irq", 0},
|
|
|
|
{"softirq", 0},
|
|
|
|
{"steal", 0},
|
|
|
|
{"guest", 0},
|
|
|
|
{"guest_nice", 0}
|
|
|
|
};
|
2021-07-18 15:44:32 +02:00
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
string get_cpuName() {
|
|
|
|
string name;
|
|
|
|
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
|
|
|
if (cpuinfo.good()) {
|
|
|
|
for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");)
|
|
|
|
cpuinfo.ignore(SSmax, '\n');
|
|
|
|
if (cpuinfo.bad()) return name;
|
2021-09-17 14:25:54 +02:00
|
|
|
else if (not cpuinfo.eof()) {
|
|
|
|
cpuinfo.ignore(1);
|
|
|
|
getline(cpuinfo, name);
|
|
|
|
}
|
|
|
|
else if (fs::exists("/sys/devices")) {
|
|
|
|
for (const auto& d : fs::directory_iterator("/sys/devices")) {
|
|
|
|
if (string(d.path().filename()).starts_with("arm")) {
|
|
|
|
name = d.path().filename();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (not name.empty()) {
|
|
|
|
auto name_vec = ssplit(name, '_');
|
|
|
|
if (name_vec.size() < 2) return capitalize(name);
|
|
|
|
else return capitalize(name_vec.at(1)) + (name_vec.size() > 2 ? ' ' + capitalize(name_vec.at(2)) : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
auto name_vec = ssplit(name);
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) {
|
2021-07-29 23:40:56 +02:00
|
|
|
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();
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
else if (v_contains(name_vec, "Ryzen"s)) {
|
2021-07-29 23:40:56 +02:00
|
|
|
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) : "");
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
else if (s_contains(name, "Intel"s) and v_contains(name_vec, "CPU"s)) {
|
2021-07-29 23:40:56 +02:00
|
|
|
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();
|
2021-10-26 23:41:40 +02:00
|
|
|
for (const auto& replace : {"Processor", "CPU", "(R)", "(TM)", "Intel", "AMD", "Core"}) {
|
|
|
|
name = s_replace(name, replace, "");
|
2021-10-26 23:50:28 +02:00
|
|
|
name = s_replace(name, " ", " ");
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
|
|
|
name = trim(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
bool get_sensors() {
|
|
|
|
bool got_cpu = false, got_coretemp = false;
|
|
|
|
vector<fs::path> search_paths;
|
|
|
|
try {
|
|
|
|
//? Setup up paths to search for sensors
|
|
|
|
if (fs::exists(fs::path("/sys/class/hwmon")) and access("/sys/class/hwmon", R_OK) != -1) {
|
|
|
|
for (const auto& dir : fs::directory_iterator(fs::path("/sys/class/hwmon"))) {
|
|
|
|
fs::path add_path = fs::canonical(dir.path());
|
|
|
|
if (v_contains(search_paths, add_path) or v_contains(search_paths, add_path / "device")) continue;
|
|
|
|
|
|
|
|
if (s_contains(add_path, "coretemp"))
|
|
|
|
got_coretemp = true;
|
|
|
|
|
2021-11-17 00:08:05 +01:00
|
|
|
for (const auto & file : fs::directory_iterator(add_path)) {
|
|
|
|
if (string(file.path().filename()) == "device") {
|
|
|
|
for (const auto & dev_file : fs::directory_iterator(file.path())) {
|
|
|
|
string dev_filename = dev_file.path().filename();
|
|
|
|
if (dev_filename.starts_with("temp") and dev_filename.ends_with("_input")) {
|
|
|
|
search_paths.push_back(file.path());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string filename = file.path().filename();
|
|
|
|
if (filename.starts_with("temp") and filename.ends_with("_input")) {
|
|
|
|
search_paths.push_back(add_path);
|
|
|
|
break;
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (not got_coretemp and fs::exists(fs::path("/sys/devices/platform/coretemp.0/hwmon"))) {
|
|
|
|
for (auto& d : fs::directory_iterator(fs::path("/sys/devices/platform/coretemp.0/hwmon"))) {
|
|
|
|
fs::path add_path = fs::canonical(d.path());
|
|
|
|
|
2021-11-17 00:08:05 +01:00
|
|
|
for (const auto & file : fs::directory_iterator(add_path)) {
|
|
|
|
string filename = file.path().filename();
|
|
|
|
if (filename.starts_with("temp") and filename.ends_with("_input") and not v_contains(search_paths, add_path)) {
|
|
|
|
search_paths.push_back(add_path);
|
|
|
|
got_coretemp = true;
|
|
|
|
break;
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//? Scan any found directories for temperature sensors
|
|
|
|
if (not search_paths.empty()) {
|
|
|
|
for (const auto& path : search_paths) {
|
|
|
|
const string pname = readfile(path / "name", path.filename());
|
2021-11-17 00:08:05 +01:00
|
|
|
for (const auto & file : fs::directory_iterator(path)) {
|
|
|
|
const string file_suffix = "input";
|
|
|
|
const int file_id = atoi(file.path().filename().c_str() + 4); // skip "temp" prefix
|
|
|
|
string file_path = file.path();
|
|
|
|
|
|
|
|
if (!s_contains(file_path, file_suffix)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const string basepath = file_path.erase(file_path.find(file_suffix), file_suffix.length());
|
|
|
|
const string label = readfile(fs::path(basepath + "label"), "temp" + to_string(file_id));
|
2021-08-03 23:47:46 +02:00
|
|
|
const string sensor_name = pname + "/" + label;
|
|
|
|
const int64_t temp = stol(readfile(fs::path(basepath + "input"), "0")) / 1000;
|
|
|
|
const int64_t high = stol(readfile(fs::path(basepath + "max"), "80000")) / 1000;
|
|
|
|
const int64_t crit = stol(readfile(fs::path(basepath + "crit"), "95000")) / 1000;
|
|
|
|
|
|
|
|
found_sensors[sensor_name] = {fs::path(basepath + "input"), label, temp, high, crit};
|
|
|
|
|
|
|
|
if (not got_cpu and (label.starts_with("Package id") or label.starts_with("Tdie"))) {
|
|
|
|
got_cpu = true;
|
|
|
|
cpu_sensor = sensor_name;
|
|
|
|
}
|
|
|
|
else if (label.starts_with("Core") or label.starts_with("Tccd")) {
|
|
|
|
got_coretemp = true;
|
|
|
|
if (not v_contains(core_sensors, sensor_name)) core_sensors.push_back(sensor_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//? If no good candidate for cpu temp has been found scan /sys/class/thermal
|
|
|
|
if (not got_cpu and fs::exists(fs::path("/sys/class/thermal"))) {
|
|
|
|
const string rootpath = fs::path("/sys/class/thermal/thermal_zone");
|
|
|
|
for (int i = 0; fs::exists(fs::path(rootpath + to_string(i))); i++) {
|
|
|
|
const fs::path basepath = rootpath + to_string(i);
|
|
|
|
if (not fs::exists(basepath / "temp")) continue;
|
|
|
|
const string label = readfile(basepath / "type", "temp" + to_string(i));
|
|
|
|
const string sensor_name = "thermal" + to_string(i) + "/" + label;
|
|
|
|
const int64_t temp = stol(readfile(basepath / "temp", "0")) / 1000;
|
|
|
|
|
|
|
|
int64_t high, crit;
|
|
|
|
for (int ii = 0; fs::exists(basepath / string("trip_point_" + to_string(ii) + "_temp")); ii++) {
|
|
|
|
const string trip_type = readfile(basepath / string("trip_point_" + to_string(ii) + "_type"));
|
|
|
|
if (not is_in(trip_type, "high", "critical")) continue;
|
|
|
|
auto& val = (trip_type == "high" ? high : crit);
|
|
|
|
val = stol(readfile(basepath / string("trip_point_" + to_string(ii) + "_temp"), "0")) / 1000;
|
|
|
|
}
|
|
|
|
if (high < 1) high = 80;
|
|
|
|
if (crit < 1) crit = 95;
|
|
|
|
|
|
|
|
found_sensors[sensor_name] = {basepath / "temp", label, temp, high, crit};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
catch (...) {}
|
|
|
|
|
2021-11-27 18:24:40 +01:00
|
|
|
if (not got_coretemp or core_sensors.empty()) {
|
|
|
|
cpu_temp_only = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rng::sort(core_sensors, rng::less{});
|
|
|
|
rng::stable_sort(core_sensors, [](const auto& a, const auto& b){
|
|
|
|
return a.size() < b.size();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
if (cpu_sensor.empty() and not found_sensors.empty()) {
|
|
|
|
for (const auto& [name, sensor] : found_sensors) {
|
2021-10-26 07:26:52 +02:00
|
|
|
if (s_contains(str_to_lower(name), "cpu") or s_contains(str_to_lower(name), "k10temp")) {
|
2021-10-24 20:17:54 +02:00
|
|
|
cpu_sensor = name;
|
|
|
|
break;
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
if (cpu_sensor.empty()) {
|
|
|
|
cpu_sensor = found_sensors.begin()->first;
|
|
|
|
Logger::warning("No good candidate for cpu sensor found, using random from all found sensors.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return not found_sensors.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void update_sensors() {
|
|
|
|
if (cpu_sensor.empty()) return;
|
|
|
|
|
|
|
|
const auto& cpu_sensor = (not Config::getS("cpu_sensor").empty() and found_sensors.contains(Config::getS("cpu_sensor")) ? Config::getS("cpu_sensor") : Cpu::cpu_sensor);
|
|
|
|
|
|
|
|
found_sensors.at(cpu_sensor).temp = stol(readfile(found_sensors.at(cpu_sensor).path, "0")) / 1000;
|
|
|
|
current_cpu.temp.at(0).push_back(found_sensors.at(cpu_sensor).temp);
|
|
|
|
current_cpu.temp_max = found_sensors.at(cpu_sensor).crit;
|
|
|
|
if (current_cpu.temp.at(0).size() > 20) current_cpu.temp.at(0).pop_front();
|
|
|
|
|
|
|
|
if (Config::getB("show_coretemp") and not cpu_temp_only) {
|
|
|
|
vector<string> done;
|
|
|
|
for (const auto& sensor : core_sensors) {
|
|
|
|
if (v_contains(done, sensor)) continue;
|
|
|
|
found_sensors.at(sensor).temp = stol(readfile(found_sensors.at(sensor).path, "0")) / 1000;
|
|
|
|
done.push_back(sensor);
|
|
|
|
}
|
|
|
|
for (const auto& [core, temp] : core_mapping) {
|
2021-08-11 20:25:11 +02:00
|
|
|
if (cmp_less(core + 1, current_cpu.temp.size()) and cmp_less(temp, core_sensors.size())) {
|
2021-08-03 23:47:46 +02:00
|
|
|
current_cpu.temp.at(core + 1).push_back(found_sensors.at(core_sensors.at(temp)).temp);
|
|
|
|
if (current_cpu.temp.at(core + 1).size() > 20) current_cpu.temp.at(core + 1).pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
string get_cpuHz() {
|
2021-08-03 23:47:46 +02:00
|
|
|
static int failed = 0;
|
|
|
|
if (failed > 4) return ""s;
|
2021-07-29 23:40:56 +02:00
|
|
|
string cpuhz;
|
|
|
|
try {
|
2021-08-03 23:47:46 +02:00
|
|
|
double hz = 0.0;
|
|
|
|
//? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster)
|
|
|
|
if (not freq_path.empty()) {
|
|
|
|
hz = stod(readfile(freq_path, "0.0")) / 1000;
|
|
|
|
if (hz <= 0.0 and ++failed >= 2)
|
|
|
|
freq_path.clear();
|
|
|
|
}
|
|
|
|
//? If freq from /sys failed or is missing try to use /proc/cpuinfo
|
|
|
|
if (hz <= 0.0) {
|
|
|
|
ifstream cpufreq(Shared::procPath / "cpuinfo");
|
|
|
|
if (cpufreq.good()) {
|
|
|
|
while (cpufreq.ignore(SSmax, '\n')) {
|
|
|
|
if (cpufreq.peek() == 'c') {
|
|
|
|
cpufreq.ignore(SSmax, ' ');
|
|
|
|
if (cpufreq.peek() == 'M') {
|
|
|
|
cpufreq.ignore(SSmax, ':');
|
|
|
|
cpufreq.ignore(1);
|
|
|
|
cpufreq >> hz;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
|
|
|
|
if (hz <= 1 or hz >= 1000000) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo.");
|
|
|
|
|
|
|
|
if (hz >= 1000) {
|
|
|
|
if (hz >= 10000) cpuhz = to_string((int)round(hz / 1000)); // Future proof until we reach THz speeds :)
|
|
|
|
else cpuhz = to_string(round(hz / 100) / 10.0).substr(0, 3);
|
|
|
|
cpuhz += " GHz";
|
|
|
|
}
|
|
|
|
else if (hz > 0)
|
|
|
|
cpuhz = to_string((int)round(hz)) + " MHz";
|
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
catch (const std::exception& e) {
|
|
|
|
if (++failed < 5) return ""s;
|
|
|
|
else {
|
|
|
|
Logger::warning("get_cpuHZ() : " + (string)e.what());
|
|
|
|
return ""s;
|
|
|
|
}
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return cpuhz;
|
|
|
|
}
|
2021-07-18 15:44:32 +02:00
|
|
|
|
2021-08-04 00:11:50 +02:00
|
|
|
auto get_core_mapping() -> unordered_flat_map<int, int> {
|
2021-08-03 23:47:46 +02:00
|
|
|
unordered_flat_map<int, int> core_map;
|
2021-09-17 14:25:54 +02:00
|
|
|
if (cpu_temp_only) return core_map;
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
//? Try to get core mapping from /proc/cpuinfo
|
|
|
|
ifstream cpuinfo(Shared::procPath / "cpuinfo");
|
2021-08-03 23:47:46 +02:00
|
|
|
if (cpuinfo.good()) {
|
2021-09-17 14:25:54 +02:00
|
|
|
int cpu, core, n = 0;
|
2021-08-03 23:47:46 +02:00
|
|
|
for (string instr; cpuinfo >> instr;) {
|
|
|
|
if (instr == "processor") {
|
|
|
|
cpuinfo.ignore(SSmax, ':');
|
|
|
|
cpuinfo >> cpu;
|
|
|
|
}
|
|
|
|
else if (instr.starts_with("core")) {
|
|
|
|
cpuinfo.ignore(SSmax, ':');
|
|
|
|
cpuinfo >> core;
|
2021-09-17 14:25:54 +02:00
|
|
|
if (std::cmp_greater_equal(core, core_sensors.size())) {
|
|
|
|
if (std::cmp_greater_equal(n, core_sensors.size())) n = 0;
|
|
|
|
core_map[cpu] = n++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
core_map[cpu] = core;
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
cpuinfo.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-17 14:25:54 +02:00
|
|
|
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc.
|
2021-08-11 20:25:11 +02:00
|
|
|
if (cmp_less(core_map.size(), Shared::coreCount)) {
|
2021-08-10 20:20:33 +02:00
|
|
|
if (Shared::coreCount % 2 == 0 and (long)core_map.size() == Shared::coreCount / 2) {
|
2021-09-17 14:25:54 +02:00
|
|
|
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++;
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
core_map.clear();
|
2021-09-17 14:25:54 +02:00
|
|
|
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++;
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-10 20:20:33 +02:00
|
|
|
//? Apply user set custom mapping if any
|
2021-08-03 23:47:46 +02:00
|
|
|
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));
|
2021-09-17 14:25:54 +02:00
|
|
|
if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue;
|
2021-08-10 20:20:33 +02:00
|
|
|
core_map.at(change_id) = new_id;
|
2021-08-03 23:47:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (...) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return core_map;
|
|
|
|
}
|
|
|
|
|
2021-10-17 22:26:43 +02:00
|
|
|
struct battery {
|
|
|
|
fs::path base_dir, energy_now, energy_full, power_now, status, online;
|
|
|
|
string device_type;
|
|
|
|
bool use_energy = true;
|
|
|
|
};
|
|
|
|
|
2021-09-17 14:25:54 +02:00
|
|
|
auto get_battery() -> tuple<int, long, string> {
|
|
|
|
if (not has_battery) return {0, 0, ""};
|
2021-10-17 22:26:43 +02:00
|
|
|
static string auto_sel;
|
|
|
|
static unordered_flat_map<string, battery> batteries;
|
2021-09-17 14:25:54 +02:00
|
|
|
|
|
|
|
//? Get paths to needed files and check for valid values on first run
|
2021-10-17 22:26:43 +02:00
|
|
|
if (batteries.empty() and has_battery) {
|
2021-09-17 14:25:54 +02:00
|
|
|
if (fs::exists("/sys/class/power_supply")) {
|
|
|
|
for (const auto& d : fs::directory_iterator("/sys/class/power_supply")) {
|
2021-10-17 16:31:25 +02:00
|
|
|
//? Only consider online power supplies of type Battery or UPS
|
|
|
|
//? see kernel docs for details on the file structure and contents
|
|
|
|
//? https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power
|
2021-10-17 22:26:43 +02:00
|
|
|
battery new_bat;
|
|
|
|
fs::path bat_dir;
|
2021-10-17 17:06:18 +02:00
|
|
|
try {
|
|
|
|
if (not d.is_directory()
|
2021-10-17 22:26:43 +02:00
|
|
|
or not fs::exists(d.path() / "type")
|
2021-10-17 17:06:18 +02:00
|
|
|
or not fs::exists(d.path() / "present")
|
|
|
|
or stoi(readfile(d.path() / "present")) != 1)
|
|
|
|
continue;
|
2021-10-17 22:26:43 +02:00
|
|
|
string dev_type = readfile(d.path() / "type");
|
|
|
|
if (is_in(dev_type, "Battery", "UPS")) {
|
2021-10-17 17:06:18 +02:00
|
|
|
bat_dir = d.path();
|
2021-10-17 22:26:43 +02:00
|
|
|
new_bat.base_dir = d.path();
|
|
|
|
new_bat.device_type = dev_type;
|
2021-10-17 17:06:18 +02:00
|
|
|
}
|
|
|
|
} catch (...) {
|
|
|
|
//? skip power supplies not conforming to the kernel standard
|
2021-10-17 16:31:25 +02:00
|
|
|
continue;
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
2021-10-17 22:26:43 +02:00
|
|
|
|
|
|
|
if (fs::exists(bat_dir / "energy_now")) new_bat.energy_now = bat_dir / "energy_now";
|
|
|
|
else if (fs::exists(bat_dir / "charge_now")) new_bat.energy_now = bat_dir / "charge_now";
|
|
|
|
else new_bat.use_energy = false;
|
|
|
|
|
|
|
|
if (fs::exists(bat_dir / "energy_full")) new_bat.energy_full = bat_dir / "energy_full";
|
|
|
|
else if (fs::exists(bat_dir / "charge_full")) new_bat.energy_full = bat_dir / "charge_full";
|
|
|
|
else new_bat.use_energy = false;
|
|
|
|
|
|
|
|
if (not new_bat.use_energy and not fs::exists(bat_dir / "capacity")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fs::exists(bat_dir / "power_now")) new_bat.power_now = bat_dir / "power_now";
|
|
|
|
else if (fs::exists(bat_dir / "current_now")) new_bat.power_now = bat_dir / "current_now";
|
|
|
|
|
|
|
|
if (fs::exists(bat_dir / "AC0/online")) new_bat.online = bat_dir / "AC0/online";
|
|
|
|
else if (fs::exists(bat_dir / "AC/online")) new_bat.online = bat_dir / "AC/online";
|
|
|
|
|
|
|
|
batteries[bat_dir.filename()] = new_bat;
|
|
|
|
Config::available_batteries.push_back(bat_dir.filename());
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-17 22:26:43 +02:00
|
|
|
if (batteries.empty()) {
|
2021-09-17 14:25:54 +02:00
|
|
|
has_battery = false;
|
|
|
|
return {0, 0, ""};
|
|
|
|
}
|
2021-10-17 22:26:43 +02:00
|
|
|
}
|
2021-09-17 14:25:54 +02:00
|
|
|
|
2021-10-17 22:26:43 +02:00
|
|
|
auto& battery_sel = Config::getS("selected_battery");
|
2021-09-17 14:25:54 +02:00
|
|
|
|
2021-10-17 22:55:36 +02:00
|
|
|
if (auto_sel.empty()) {
|
2021-10-17 22:26:43 +02:00
|
|
|
for (auto& [name, bat] : batteries) {
|
|
|
|
if (bat.device_type == "Battery") {
|
|
|
|
auto_sel = name;
|
|
|
|
break;
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-17 22:26:43 +02:00
|
|
|
if (auto_sel.empty()) auto_sel = batteries.begin()->first;
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
|
2021-10-17 22:26:43 +02:00
|
|
|
auto& b = (battery_sel != "Auto" and batteries.contains(battery_sel) ? batteries.at(battery_sel) : batteries.at(auto_sel));
|
|
|
|
|
2021-09-17 14:25:54 +02:00
|
|
|
int percent = -1;
|
|
|
|
long seconds = -1;
|
|
|
|
|
|
|
|
//? Try to get battery percentage
|
2021-10-17 22:26:43 +02:00
|
|
|
if (b.use_energy) {
|
2021-09-17 14:25:54 +02:00
|
|
|
try {
|
2021-10-17 22:26:43 +02:00
|
|
|
percent = round(100.0 * stoll(readfile(b.energy_now, "-1")) / stoll(readfile(b.energy_full, "1")));
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
catch (const std::invalid_argument&) { }
|
|
|
|
catch (const std::out_of_range&) { }
|
|
|
|
}
|
|
|
|
if (percent < 0) {
|
|
|
|
try {
|
2021-10-17 22:26:43 +02:00
|
|
|
percent = stoll(readfile(b.base_dir / "capacity", "-1"));
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
catch (const std::invalid_argument&) { }
|
|
|
|
catch (const std::out_of_range&) { }
|
|
|
|
}
|
|
|
|
if (percent < 0) {
|
|
|
|
has_battery = false;
|
|
|
|
return {0, 0, ""};
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get charging/discharging status
|
2021-10-17 22:26:43 +02:00
|
|
|
string status = str_to_lower(readfile(b.base_dir / "status", "unknown"));
|
|
|
|
if (status == "unknown" and not b.online.empty()) {
|
|
|
|
const auto online = readfile(b.online, "0");
|
2021-09-17 14:25:54 +02:00
|
|
|
if (online == "1" and percent < 100) status = "charging";
|
|
|
|
else if (online == "1") status = "full";
|
|
|
|
else status = "discharging";
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get seconds to empty
|
|
|
|
if (not is_in(status, "charging", "full")) {
|
2021-10-17 22:26:43 +02:00
|
|
|
if (b.use_energy and not b.power_now.empty()) {
|
2021-09-17 14:25:54 +02:00
|
|
|
try {
|
2021-10-17 22:26:43 +02:00
|
|
|
seconds = round((double)stoll(readfile(b.energy_now, "0")) / stoll(readfile(b.power_now, "1")) * 3600);
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
catch (const std::invalid_argument&) { }
|
|
|
|
catch (const std::out_of_range&) { }
|
|
|
|
}
|
2021-10-17 22:26:43 +02:00
|
|
|
if (seconds < 0 and fs::exists(b.base_dir / "time_to_empty")) {
|
2021-09-17 14:25:54 +02:00
|
|
|
try {
|
2021-10-17 22:26:43 +02:00
|
|
|
seconds = stoll(readfile(b.base_dir / "time_to_empty", "0")) * 60;
|
2021-09-17 14:25:54 +02:00
|
|
|
}
|
|
|
|
catch (const std::invalid_argument&) { }
|
|
|
|
catch (const std::out_of_range&) { }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {percent, seconds, status};
|
|
|
|
}
|
|
|
|
|
2021-08-15 23:20:55 +02:00
|
|
|
auto collect(const bool no_update) -> cpu_info& {
|
2021-08-03 23:47:46 +02:00
|
|
|
if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) return current_cpu;
|
2021-07-29 23:40:56 +02:00
|
|
|
auto& cpu = current_cpu;
|
|
|
|
|
|
|
|
ifstream cread;
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
try {
|
|
|
|
//? Get cpu load averages from /proc/loadavg
|
|
|
|
cread.open(Shared::procPath / "loadavg");
|
|
|
|
if (cread.good()) {
|
|
|
|
cread >> cpu.load_avg[0] >> cpu.load_avg[1] >> cpu.load_avg[2];
|
|
|
|
}
|
|
|
|
cread.close();
|
|
|
|
|
|
|
|
//? Get cpu total times for all cores from /proc/stat
|
|
|
|
cread.open(Shared::procPath / "stat");
|
|
|
|
for (int i = 0; cread.good() and cread.peek() == 'c'; i++) {
|
|
|
|
cread.ignore(SSmax, ' ');
|
2021-07-29 23:40:56 +02:00
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
//? Expected on kernel 2.6.3> : 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice
|
|
|
|
vector<long long> times;
|
|
|
|
long long total_sum = 0;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
for (uint64_t val; cread >> val; total_sum += val) {
|
|
|
|
times.push_back(val);
|
|
|
|
}
|
|
|
|
cread.clear();
|
|
|
|
if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat");
|
|
|
|
|
|
|
|
//? Subtract fields 8-9 and any future unknown fields
|
2021-08-03 23:47:46 +02:00
|
|
|
const long long totals = max(0ll, total_sum - (times.size() > 8 ? std::accumulate(times.begin() + 8, times.end(), 0) : 0));
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Add iowait field if present
|
2021-08-03 23:47:46 +02:00
|
|
|
const long long idles = max(0ll, times.at(3) + (times.size() > 4 ? times.at(4) : 0));
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Calculate values for totals from first line of stat
|
|
|
|
if (i == 0) {
|
2021-08-03 23:47:46 +02:00
|
|
|
const long long calc_totals = max(1ll, totals - cpu_old.at("totals"));
|
|
|
|
const long long calc_idles = max(1ll, idles - cpu_old.at("idles"));
|
|
|
|
cpu_old.at("totals") = totals;
|
|
|
|
cpu_old.at("idles") = idles;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Total usage of cpu
|
2021-08-03 23:47:46 +02:00
|
|
|
cpu.cpu_percent.at("total").push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(cpu.cpu_percent.at("total").size(), width * 2)) cpu.cpu_percent.at("total").pop_front();
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Populate cpu.cpu_percent with all fields from stat
|
|
|
|
for (int ii = 0; const auto& val : times) {
|
2021-08-03 23:47:46 +02:00
|
|
|
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;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(cpu.cpu_percent.at(time_names.at(ii)).size(), width * 2)) cpu.cpu_percent.at(time_names.at(ii)).pop_front();
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
if (++ii == 10) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//? Calculate cpu total for each core
|
|
|
|
else {
|
|
|
|
if (i > Shared::coreCount) break;
|
2021-08-03 23:47:46 +02:00
|
|
|
const long long calc_totals = max(0ll, totals - core_old_totals.at(i-1));
|
|
|
|
const long long calc_idles = max(0ll, idles - core_old_idles.at(i-1));
|
|
|
|
core_old_totals.at(i-1) = totals;
|
|
|
|
core_old_idles.at(i-1) = idles;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
cpu.core_percent.at(i-1).push_back(clamp((long long)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ll, 100ll));
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
//? Reduce size if there are more values than needed for graph
|
2021-08-03 23:47:46 +02:00
|
|
|
if (cpu.core_percent.at(i-1).size() > 40) cpu.core_percent.at(i-1).pop_front();
|
2021-07-29 23:40:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
catch (const std::exception& e) {
|
|
|
|
Logger::debug("get_cpuHz() : " + (string)e.what());
|
|
|
|
if (cread.bad()) throw std::runtime_error("Failed to read /proc/stat");
|
|
|
|
else throw std::runtime_error("collect() : " + (string)e.what());
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Config::getB("show_cpu_freq"))
|
|
|
|
cpuHz = get_cpuHz();
|
|
|
|
|
2021-08-03 23:47:46 +02:00
|
|
|
if (Config::getB("check_temp") and got_sensors)
|
|
|
|
update_sensors();
|
|
|
|
|
2021-09-17 14:25:54 +02:00
|
|
|
if (Config::getB("show_battery") and has_battery)
|
|
|
|
current_bat = get_battery();
|
|
|
|
|
2021-07-29 23:40:56 +02:00
|
|
|
return cpu;
|
2021-07-18 15:44:32 +02:00
|
|
|
}
|
2021-07-04 22:02:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace Mem {
|
|
|
|
bool has_swap = false;
|
2021-08-10 20:20:33 +02:00
|
|
|
vector<string> fstab;
|
|
|
|
fs::file_time_type fstab_time;
|
|
|
|
int disk_ios = 0;
|
2021-08-17 22:33:21 +02:00
|
|
|
vector<string> last_found;
|
2022-04-30 05:08:00 +02:00
|
|
|
const std::regex zfs_size_regex("^size\\s+\\d\\s+(\\d+)");
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
mem_info current_mem {};
|
2021-07-18 15:44:32 +02:00
|
|
|
|
2021-10-05 09:18:04 +02:00
|
|
|
uint64_t get_totalMem() {
|
|
|
|
ifstream meminfo(Shared::procPath / "meminfo");
|
|
|
|
int64_t totalMem;
|
|
|
|
if (meminfo.good()) {
|
|
|
|
meminfo.ignore(SSmax, ':');
|
|
|
|
meminfo >> totalMem;
|
|
|
|
totalMem <<= 10;
|
|
|
|
}
|
|
|
|
if (not meminfo.good() or totalMem == 0)
|
|
|
|
throw std::runtime_error("Could not get total memory size from /proc/meminfo");
|
2021-10-06 18:06:05 +02:00
|
|
|
|
2021-10-05 09:18:04 +02:00
|
|
|
return totalMem;
|
|
|
|
}
|
|
|
|
|
2021-08-15 23:20:55 +02:00
|
|
|
auto collect(const bool no_update) -> mem_info& {
|
2021-08-23 17:35:27 +02:00
|
|
|
if (Runner::stopping or (no_update and not current_mem.percent.at("used").empty())) return current_mem;
|
2021-08-10 20:20:33 +02:00
|
|
|
auto& show_swap = Config::getB("show_swap");
|
|
|
|
auto& swap_disk = Config::getB("swap_disk");
|
|
|
|
auto& show_disks = Config::getB("show_disks");
|
2022-04-30 05:08:00 +02:00
|
|
|
auto& zfs_arc_cached = Config::getB("zfs_arc_cached");
|
2021-10-05 09:18:04 +02:00
|
|
|
auto totalMem = get_totalMem();
|
2021-08-10 20:20:33 +02:00
|
|
|
auto& mem = current_mem;
|
|
|
|
|
|
|
|
mem.stats.at("swap_total") = 0;
|
|
|
|
|
2022-04-30 05:08:00 +02:00
|
|
|
//? Read ZFS ARC info from /proc/spl/kstat/zfs/arcstats
|
|
|
|
uint64_t arc_size = 0;
|
|
|
|
if (zfs_arc_cached) {
|
|
|
|
ifstream arcstats(Shared::procPath / "spl/kstat/zfs/arcstats");
|
|
|
|
if (arcstats.good()) {
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(arcstats, line)) {
|
|
|
|
std::smatch match;
|
|
|
|
if (std::regex_match(line, match, zfs_size_regex) && match.size() == 2) {
|
|
|
|
arc_size = stoull(match.str(1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
arcstats.close();
|
|
|
|
}
|
|
|
|
|
2021-08-10 20:20:33 +02:00
|
|
|
//? Read memory info from /proc/meminfo
|
|
|
|
ifstream meminfo(Shared::procPath / "meminfo");
|
|
|
|
if (meminfo.good()) {
|
|
|
|
bool got_avail = false;
|
2021-11-22 21:43:40 +01:00
|
|
|
for (string label; meminfo.peek() != 'D' and meminfo >> label;) {
|
2021-08-10 20:20:33 +02:00
|
|
|
if (label == "MemFree:") {
|
|
|
|
meminfo >> mem.stats.at("free");
|
|
|
|
mem.stats.at("free") <<= 10;
|
|
|
|
}
|
|
|
|
else if (label == "MemAvailable:") {
|
|
|
|
meminfo >> mem.stats.at("available");
|
|
|
|
mem.stats.at("available") <<= 10;
|
|
|
|
got_avail = true;
|
|
|
|
}
|
|
|
|
else if (label == "Cached:") {
|
|
|
|
meminfo >> mem.stats.at("cached");
|
|
|
|
mem.stats.at("cached") <<= 10;
|
|
|
|
if (not show_swap and not swap_disk) break;
|
|
|
|
}
|
|
|
|
else if (label == "SwapTotal:") {
|
|
|
|
meminfo >> mem.stats.at("swap_total");
|
|
|
|
mem.stats.at("swap_total") <<= 10;
|
|
|
|
}
|
|
|
|
else if (label == "SwapFree:") {
|
|
|
|
meminfo >> mem.stats.at("swap_free");
|
|
|
|
mem.stats.at("swap_free") <<= 10;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
meminfo.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
if (not got_avail) mem.stats.at("available") = mem.stats.at("free") + mem.stats.at("cached");
|
2022-04-30 05:08:00 +02:00
|
|
|
if (zfs_arc_cached) {
|
|
|
|
mem.stats.at("cached") += arc_size;
|
|
|
|
mem.stats.at("available") += arc_size;
|
|
|
|
}
|
2021-10-05 09:18:04 +02:00
|
|
|
mem.stats.at("used") = totalMem - mem.stats.at("available");
|
2021-08-10 20:20:33 +02:00
|
|
|
if (mem.stats.at("swap_total") > 0) mem.stats.at("swap_used") = mem.stats.at("swap_total") - mem.stats.at("swap_free");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw std::runtime_error("Failed to read /proc/meminfo");
|
|
|
|
|
|
|
|
meminfo.close();
|
|
|
|
|
|
|
|
//? Calculate percentages
|
|
|
|
for (const auto& name : mem_names) {
|
2021-10-05 09:18:04 +02:00
|
|
|
mem.percent.at(name).push_back(round((double)mem.stats.at(name) * 100 / totalMem));
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front();
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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")));
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(mem.percent.at(name).size(), width * 2)) mem.percent.at(name).pop_front();
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
has_swap = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
has_swap = false;
|
|
|
|
|
|
|
|
//? Get disks stats
|
2021-08-17 22:33:21 +02:00
|
|
|
if (show_disks) {
|
2021-09-01 21:40:13 +02:00
|
|
|
double uptime = system_uptime();
|
2022-02-13 00:33:20 +01:00
|
|
|
auto free_priv = Config::getB("disk_free_priv");
|
2021-08-10 20:20:33 +02:00
|
|
|
try {
|
|
|
|
auto& disks_filter = Config::getS("disks_filter");
|
|
|
|
bool filter_exclude = false;
|
|
|
|
auto& use_fstab = Config::getB("use_fstab");
|
|
|
|
auto& only_physical = Config::getB("only_physical");
|
|
|
|
auto& disks = mem.disks;
|
|
|
|
ifstream diskread;
|
|
|
|
|
|
|
|
vector<string> 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get list of "real" filesystems from /proc/filesystems
|
|
|
|
vector<string> fstypes;
|
|
|
|
if (only_physical and not use_fstab) {
|
|
|
|
fstypes = {"zfs", "wslfs", "drvfs"};
|
|
|
|
diskread.open(Shared::procPath / "filesystems");
|
|
|
|
if (diskread.good()) {
|
|
|
|
for (string fstype; diskread >> fstype;) {
|
|
|
|
if (not is_in(fstype, "nodev", "squashfs", "nullfs"))
|
|
|
|
fstypes.push_back(fstype);
|
|
|
|
diskread.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw std::runtime_error("Failed to read /proc/filesystems");
|
|
|
|
diskread.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get disk list to use from fstab if enabled
|
|
|
|
if (use_fstab and fs::last_write_time("/etc/fstab") != fstab_time) {
|
|
|
|
fstab.clear();
|
|
|
|
fstab_time = fs::last_write_time("/etc/fstab");
|
|
|
|
diskread.open("/etc/fstab");
|
|
|
|
if (diskread.good()) {
|
|
|
|
for (string instr; diskread >> instr;) {
|
|
|
|
if (not instr.starts_with('#')) {
|
|
|
|
diskread >> instr;
|
2021-10-06 11:25:10 +02:00
|
|
|
#ifdef SNAPPED
|
|
|
|
if (instr == "/") fstab.push_back("/mnt");
|
|
|
|
else if (not is_in(instr, "none", "swap")) fstab.push_back(instr);
|
|
|
|
#else
|
|
|
|
if (not is_in(instr, "none", "swap")) fstab.push_back(instr);
|
|
|
|
#endif
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
diskread.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw std::runtime_error("Failed to read /etc/fstab");
|
|
|
|
diskread.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get mounts from /etc/mtab or /proc/self/mounts
|
|
|
|
diskread.open((fs::exists("/etc/mtab") ? fs::path("/etc/mtab") : Shared::procPath / "self/mounts"));
|
|
|
|
if (diskread.good()) {
|
2021-08-17 22:33:21 +02:00
|
|
|
vector<string> found;
|
|
|
|
found.reserve(last_found.size());
|
2021-08-10 20:20:33 +02:00
|
|
|
string dev, mountpoint, fstype;
|
|
|
|
while (not diskread.eof()) {
|
|
|
|
std::error_code ec;
|
|
|
|
diskread >> dev >> mountpoint >> fstype;
|
2022-02-17 22:28:10 +01:00
|
|
|
diskread.ignore(SSmax, '\n');
|
|
|
|
|
|
|
|
if (v_contains(found, mountpoint)) continue;
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
//? 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((not use_fstab and not only_physical)
|
|
|
|
or (use_fstab and v_contains(fstab, mountpoint))
|
|
|
|
or (not use_fstab and only_physical and v_contains(fstypes, fstype))) {
|
|
|
|
found.push_back(mountpoint);
|
2021-08-17 22:33:21 +02:00
|
|
|
if (not v_contains(last_found, mountpoint)) redraw = true;
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
//? Save mountpoint, name, dev path and path to /sys/block stat file
|
|
|
|
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;
|
2021-10-06 11:25:10 +02:00
|
|
|
#ifdef SNAPPED
|
|
|
|
if (mountpoint == "/mnt") disks.at(mountpoint).name = "root";
|
|
|
|
#endif
|
|
|
|
if (disks.at(mountpoint).name.empty()) disks.at(mountpoint).name = (mountpoint == "/" ? "root" : mountpoint);
|
2021-08-10 20:20:33 +02:00
|
|
|
string devname = disks.at(mountpoint).dev.filename();
|
2022-04-26 20:43:35 +02:00
|
|
|
int c = 0;
|
2021-08-10 20:20:33 +02:00
|
|
|
while (devname.size() >= 2) {
|
2021-08-17 22:33:21 +02:00
|
|
|
if (fs::exists("/sys/block/" + devname + "/stat", ec) and access(string("/sys/block/" + devname + "/stat").c_str(), R_OK) == 0) {
|
2022-04-26 20:43:35 +02:00
|
|
|
if (c > 0 and fs::exists("/sys/block/" + devname + '/' + disks.at(mountpoint).dev.filename().string() + "/stat", ec))
|
|
|
|
disks.at(mountpoint).stat = "/sys/block/" + devname + '/' + disks.at(mountpoint).dev.filename().string() + "/stat";
|
|
|
|
else
|
|
|
|
disks.at(mountpoint).stat = "/sys/block/" + devname + "/stat";
|
2021-08-10 20:20:33 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
devname.resize(devname.size() - 1);
|
2022-04-26 20:43:35 +02:00
|
|
|
c++;
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//? Remove disks no longer mounted or filtered out
|
|
|
|
if (swap_disk and has_swap) found.push_back("swap");
|
2021-08-15 23:20:55 +02:00
|
|
|
for (auto it = disks.begin(); it != disks.end();) {
|
|
|
|
if (not v_contains(found, it->first))
|
|
|
|
it = disks.erase(it);
|
2021-08-10 20:20:33 +02:00
|
|
|
else
|
2021-08-15 23:20:55 +02:00
|
|
|
it++;
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
2021-09-17 14:25:54 +02:00
|
|
|
if (found.size() != last_found.size()) redraw = true;
|
2021-08-17 22:33:21 +02:00
|
|
|
last_found = std::move(found);
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
throw std::runtime_error("Failed to get mounts from /etc/mtab and /proc/self/mounts");
|
|
|
|
diskread.close();
|
|
|
|
|
|
|
|
//? Get disk/partition stats
|
|
|
|
for (auto& [mountpoint, disk] : disks) {
|
2021-08-17 22:33:21 +02:00
|
|
|
if (std::error_code ec; not fs::exists(mountpoint, ec)) continue;
|
2021-11-03 21:01:34 +01:00
|
|
|
struct statvfs64 vfs;
|
|
|
|
if (statvfs64(mountpoint.c_str(), &vfs) < 0) {
|
2021-08-10 20:20:33 +02:00
|
|
|
Logger::warning("Failed to get disk/partition stats with statvfs() for: " + mountpoint);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
disk.total = vfs.f_blocks * vfs.f_frsize;
|
2022-02-13 00:33:20 +01:00
|
|
|
disk.free = (free_priv ? vfs.f_bfree : vfs.f_bavail) * vfs.f_frsize;
|
2021-08-10 20:20:33 +02:00
|
|
|
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();
|
2021-10-06 11:25:10 +02:00
|
|
|
#ifdef SNAPPED
|
|
|
|
if (disks.contains("/mnt")) mem.disks_order.push_back("/mnt");
|
|
|
|
#else
|
|
|
|
if (disks.contains("/")) mem.disks_order.push_back("/");
|
|
|
|
#endif
|
2021-08-10 20:20:33 +02:00
|
|
|
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();
|
|
|
|
}
|
2021-08-17 22:33:21 +02:00
|
|
|
for (const auto& name : last_found)
|
2021-10-06 18:06:05 +02:00
|
|
|
#ifdef SNAPPED
|
|
|
|
if (not is_in(name, "/mnt", "swap")) mem.disks_order.push_back(name);
|
|
|
|
#else
|
|
|
|
if (not is_in(name, "/", "swap")) mem.disks_order.push_back(name);
|
|
|
|
#endif
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
//? Get disks IO
|
2021-09-01 21:40:13 +02:00
|
|
|
int64_t sectors_read, sectors_write, io_ticks;
|
2021-08-10 20:20:33 +02:00
|
|
|
disk_ios = 0;
|
|
|
|
for (auto& [ignored, disk] : disks) {
|
2021-08-17 22:33:21 +02:00
|
|
|
if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue;
|
2021-08-10 20:20:33 +02:00
|
|
|
diskread.open(disk.stat);
|
|
|
|
if (diskread.good()) {
|
|
|
|
disk_ios++;
|
|
|
|
for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
|
|
|
|
diskread >> sectors_read;
|
|
|
|
if (disk.io_read.empty())
|
|
|
|
disk.io_read.push_back(0);
|
|
|
|
else
|
2021-09-18 14:42:53 +02:00
|
|
|
disk.io_read.push_back(max((int64_t)0, (sectors_read - disk.old_io.at(0)) * 512));
|
2021-08-10 20:20:33 +02:00
|
|
|
disk.old_io.at(0) = sectors_read;
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front();
|
2021-08-10 20:20:33 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
|
|
|
|
diskread >> sectors_write;
|
|
|
|
if (disk.io_write.empty())
|
|
|
|
disk.io_write.push_back(0);
|
|
|
|
else
|
2021-09-18 14:42:53 +02:00
|
|
|
disk.io_write.push_back(max((int64_t)0, (sectors_write - disk.old_io.at(1)) * 512));
|
2021-08-10 20:20:33 +02:00
|
|
|
disk.old_io.at(1) = sectors_write;
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front();
|
2021-09-01 21:40:13 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); }
|
|
|
|
diskread >> io_ticks;
|
|
|
|
if (disk.io_activity.empty())
|
|
|
|
disk.io_activity.push_back(0);
|
|
|
|
else
|
|
|
|
disk.io_activity.push_back(clamp((long)round((double)(io_ticks - disk.old_io.at(2)) / (uptime - old_uptime) / 10), 0l, 100l));
|
|
|
|
disk.old_io.at(2) = io_ticks;
|
|
|
|
while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front();
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
diskread.close();
|
|
|
|
}
|
2021-09-01 21:40:13 +02:00
|
|
|
old_uptime = uptime;
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
Logger::warning("Error in Mem::collect() : " + (string)e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mem;
|
2021-07-18 15:44:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Net {
|
2021-08-15 23:20:55 +02:00
|
|
|
unordered_flat_map<string, net_info> current_net;
|
|
|
|
net_info empty_net = {};
|
|
|
|
vector<string> interfaces;
|
|
|
|
string selected_iface;
|
|
|
|
int errors = 0;
|
|
|
|
unordered_flat_map<string, uint64_t> graph_max = { {"download", {}}, {"upload", {}} };
|
|
|
|
unordered_flat_map<string, array<int, 2>> max_count = { {"download", {}}, {"upload", {}} };
|
|
|
|
bool rescale = true;
|
2021-09-01 21:40:13 +02:00
|
|
|
uint64_t timestamp = 0;
|
2021-08-15 23:20:55 +02:00
|
|
|
|
|
|
|
//* 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(const 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");
|
2021-08-22 16:04:01 +02:00
|
|
|
auto new_timestamp = time_ms();
|
2021-08-15 23:20:55 +02:00
|
|
|
|
|
|
|
if (not no_update and errors < 3) {
|
|
|
|
//? Get interface list using getifaddrs() wrapper
|
2021-08-17 22:33:21 +02:00
|
|
|
getifaddr_wrapper if_wrap {};
|
2021-08-15 23:20:55 +02:00
|
|
|
if (if_wrap.status != 0) {
|
|
|
|
errors++;
|
|
|
|
Logger::error("Net::collect() -> getifaddrs() failed with id " + to_string(if_wrap.status));
|
2021-09-01 21:40:13 +02:00
|
|
|
redraw = true;
|
2021-08-15 23:20:55 +02:00
|
|
|
return empty_net;
|
|
|
|
}
|
|
|
|
int family = 0;
|
|
|
|
char ip[NI_MAXHOST];
|
|
|
|
interfaces.clear();
|
|
|
|
string ipv4, ipv6;
|
|
|
|
|
|
|
|
//? Iteration over all items in getifaddrs() list
|
|
|
|
for (auto* ifa = if_wrap(); ifa != NULL; ifa = ifa->ifa_next) {
|
|
|
|
if (ifa->ifa_addr == NULL) continue;
|
|
|
|
family = ifa->ifa_addr->sa_family;
|
|
|
|
const auto& iface = ifa->ifa_name;
|
|
|
|
|
|
|
|
//? Get IPv4 address
|
|
|
|
if (family == AF_INET) {
|
|
|
|
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
|
|
|
|
net[iface].ipv4 = ip;
|
|
|
|
}
|
|
|
|
//? Get IPv6 address
|
|
|
|
else if (family == AF_INET6) {
|
|
|
|
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
|
|
|
|
net[iface].ipv6 = ip;
|
|
|
|
}
|
|
|
|
|
|
|
|
//? 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Get total recieved and transmitted bytes + device address if no ip was found
|
|
|
|
for (const auto& iface : interfaces) {
|
|
|
|
if (net.at(iface).ipv4.empty() and net.at(iface).ipv6.empty())
|
|
|
|
net.at(iface).ipv4 = readfile("/sys/class/net/" + iface + "/address");
|
|
|
|
|
|
|
|
for (const string dir : {"download", "upload"}) {
|
|
|
|
const fs::path sys_file = "/sys/class/net/" + iface + "/statistics/" + (dir == "download" ? "rx_bytes" : "tx_bytes");
|
|
|
|
auto& saved_stat = net.at(iface).stat.at(dir);
|
|
|
|
auto& bandwidth = net.at(iface).bandwidth.at(dir);
|
|
|
|
|
2022-01-12 20:49:27 +01:00
|
|
|
uint64_t val = 0;
|
|
|
|
try { val = (uint64_t)stoull(readfile(sys_file, "0")); }
|
2021-09-30 22:49:14 +02:00
|
|
|
catch (const std::invalid_argument&) {}
|
|
|
|
catch (const std::out_of_range&) {}
|
2021-08-15 23:20:55 +02:00
|
|
|
|
|
|
|
//? Update speed, total and top values
|
2021-12-30 11:26:23 +01:00
|
|
|
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<uint64_t>::max())) {
|
|
|
|
saved_stat.rollover = 0;
|
|
|
|
saved_stat.last = 0;
|
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
saved_stat.speed = round((double)(val - saved_stat.last) / ((double)(new_timestamp - timestamp) / 1000));
|
2021-08-15 23:20:55 +02:00
|
|
|
if (saved_stat.speed > saved_stat.top) saved_stat.top = saved_stat.speed;
|
2021-12-30 11:26:23 +01:00
|
|
|
if (saved_stat.offset > val + saved_stat.rollover) saved_stat.offset = 0;
|
|
|
|
saved_stat.total = (val + saved_stat.rollover) - saved_stat.offset;
|
2021-08-15 23:20:55 +02:00
|
|
|
saved_stat.last = val;
|
|
|
|
|
|
|
|
//? Add values to graph
|
|
|
|
bandwidth.push_back(saved_stat.speed);
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(bandwidth.size(), width * 2)) bandwidth.pop_front();
|
2021-08-15 23:20:55 +02:00
|
|
|
|
|
|
|
//? Set counters for auto scaling
|
|
|
|
if (net_auto and selected_iface == iface) {
|
2022-05-28 20:48:02 +02:00
|
|
|
if (net_sync and saved_stat.speed < net.at(iface).stat.at(dir == "download" ? "upload" : "download").speed) continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
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++;
|
|
|
|
}
|
2021-08-17 22:33:21 +02:00
|
|
|
net.compact();
|
2021-08-15 23:20:55 +02:00
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
|
|
|
|
timestamp = new_timestamp;
|
2021-08-15 23:20:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//? 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
|
2021-09-01 21:40:13 +02:00
|
|
|
if (selected_iface.empty() and not sorted_interfaces.empty()) selected_iface = sorted_interfaces.at(0);
|
|
|
|
else if (sorted_interfaces.empty()) return empty_net;
|
|
|
|
|
2021-08-15 23:20:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//? 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 uint64_t 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, 0) / 5
|
|
|
|
: net[selected_iface].stat[dir].speed);
|
2021-09-18 14:42:53 +02:00
|
|
|
graph_max[dir] = max(uint64_t(avg_speed * (sel == 0 ? 1.3 : 3.0)), (uint64_t)10 << 10);
|
2021-08-15 23:20:55 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-18 15:44:32 +02:00
|
|
|
|
2021-08-15 23:20:55 +02:00
|
|
|
rescale = false;
|
|
|
|
return net.at(selected_iface);
|
2021-07-18 15:44:32 +02:00
|
|
|
}
|
2021-07-04 22:02:31 +02:00
|
|
|
}
|
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
namespace Proc {
|
2021-08-17 22:33:21 +02:00
|
|
|
|
|
|
|
vector<proc_info> current_procs;
|
|
|
|
unordered_flat_map<string, string> uid_user;
|
2021-08-22 16:04:01 +02:00
|
|
|
string current_sort;
|
|
|
|
string current_filter;
|
|
|
|
bool current_rev = false;
|
2021-08-17 22:33:21 +02:00
|
|
|
|
|
|
|
fs::file_time_type passwd_time;
|
|
|
|
|
|
|
|
uint64_t cputimes;
|
2021-07-29 23:40:56 +02:00
|
|
|
int collapse = -1, expand = -1;
|
2021-06-19 14:57:27 +02:00
|
|
|
uint64_t old_cputimes = 0;
|
2021-07-18 15:44:32 +02:00
|
|
|
atomic<int> numpids = 0;
|
2021-08-17 22:33:21 +02:00
|
|
|
int filter_found = 0;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
detail_container detailed;
|
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
//* Generate process tree list
|
2021-08-22 16:04:01 +02:00
|
|
|
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<std::reference_wrapper<proc_info>>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false, const bool no_update=false, const bool should_filter=false) {
|
2021-06-19 14:57:27 +02:00
|
|
|
auto cur_pos = out_procs.size();
|
2021-06-27 22:13:32 +02:00
|
|
|
bool filtering = false;
|
|
|
|
|
|
|
|
//? If filtering, include children of matching processes
|
2021-08-22 16:04:01 +02:00
|
|
|
if (not found and (should_filter or not filter.empty())) {
|
2021-07-04 22:02:31 +02:00
|
|
|
if (not s_contains(std::to_string(cur_proc.pid), filter)
|
|
|
|
and not s_contains(cur_proc.name, filter)
|
|
|
|
and not s_contains(cur_proc.cmd, filter)
|
|
|
|
and not s_contains(cur_proc.user, filter)) {
|
2021-06-27 22:13:32 +02:00
|
|
|
filtering = true;
|
2021-08-22 16:04:01 +02:00
|
|
|
cur_proc.filtered = true;
|
|
|
|
filter_found++;
|
2021-06-27 22:13:32 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
found = true;
|
|
|
|
cur_depth = 0;
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 22:52:52 +02:00
|
|
|
else if (cur_proc.filtered) cur_proc.filtered = false;
|
2021-06-27 22:13:32 +02:00
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
//? Set tree index position for process if not filtered out or currently in a collapsed sub-tree
|
2021-07-15 23:49:16 +02:00
|
|
|
if (not collapsed and not filtering) {
|
2021-08-22 16:04:01 +02:00
|
|
|
out_procs.push_back(std::ref(cur_proc));
|
|
|
|
cur_proc.tree_index = out_procs.size() - 1;
|
2021-08-17 22:33:21 +02:00
|
|
|
//? Try to find name of the binary file and append to program name if not the same
|
|
|
|
if (cur_proc.short_cmd.empty() and not cur_proc.cmd.empty()) {
|
|
|
|
std::string_view cmd_view = cur_proc.cmd;
|
2021-09-18 14:42:53 +02:00
|
|
|
cmd_view = cmd_view.substr((size_t)0, min(cmd_view.find(' '), cmd_view.size()));
|
2021-08-12 22:25:18 +02:00
|
|
|
cmd_view = cmd_view.substr(min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
|
2021-08-22 16:04:01 +02:00
|
|
|
cur_proc.short_cmd = (string)cmd_view;
|
2021-07-15 23:49:16 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
else {
|
|
|
|
cur_proc.tree_index = in_procs.size();
|
|
|
|
}
|
2021-06-20 22:07:04 +02:00
|
|
|
|
2021-08-12 22:25:18 +02:00
|
|
|
//? Recursive iteration over all children
|
2021-06-19 14:57:27 +02:00
|
|
|
int children = 0;
|
2021-06-20 22:07:04 +02:00
|
|
|
for (auto& p : rng::equal_range(in_procs, cur_proc.pid, rng::less{}, &proc_info::ppid)) {
|
2021-08-22 16:04:01 +02:00
|
|
|
if (not no_update and not filtering and (collapsed or cur_proc.collapsed)) {
|
|
|
|
out_procs.back().get().cpu_p += p.cpu_p;
|
|
|
|
out_procs.back().get().mem += p.mem;
|
|
|
|
out_procs.back().get().threads += p.threads;
|
2021-09-23 12:44:46 +02:00
|
|
|
filter_found++;
|
2021-08-22 16:04:01 +02:00
|
|
|
}
|
2021-06-27 22:13:32 +02:00
|
|
|
if (collapsed and not filtering) {
|
2021-08-22 16:04:01 +02:00
|
|
|
cur_proc.filtered = true;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-27 01:19:57 +02:00
|
|
|
else children++;
|
2021-08-22 16:04:01 +02:00
|
|
|
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cur_proc.collapsed), filter, found, no_update, should_filter);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-27 22:13:32 +02:00
|
|
|
if (collapsed or filtering) return;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-08-12 22:25:18 +02:00
|
|
|
//? Add tree terminator symbol if it's the last child in a sub-tree
|
2021-08-22 16:04:01 +02:00
|
|
|
if (out_procs.size() > cur_pos + 1 and not out_procs.back().get().prefix.ends_with("]─"))
|
|
|
|
out_procs.back().get().prefix.replace(out_procs.back().get().prefix.size() - 8, 8, " └─ ");
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-08-12 22:25:18 +02:00
|
|
|
//? Add collapse/expand symbols if process have any children
|
2021-08-22 16:04:01 +02:00
|
|
|
out_procs.at(cur_pos).get().prefix = " │ "s * cur_depth + (children > 0 ? (cur_proc.collapsed ? "[+]─" : "[-]─") : " ├─ ");
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//* Get detailed info for selected process
|
2021-07-26 01:06:34 +02:00
|
|
|
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs) {
|
2021-07-29 23:40:56 +02:00
|
|
|
fs::path pid_path = Shared::procPath / std::to_string(pid);
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
if (pid != detailed.last_pid) {
|
2021-07-26 01:06:34 +02:00
|
|
|
detailed = {};
|
2021-07-04 01:18:48 +02:00
|
|
|
detailed.last_pid = pid;
|
2021-08-10 20:20:33 +02:00
|
|
|
detailed.skip_smaps = not Config::getB("proc_info_smaps");
|
2021-07-04 01:18:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//? Copy proc_info for process from proc vector
|
2021-08-10 20:20:33 +02:00
|
|
|
auto p_info = rng::find(procs, pid, &proc_info::pid);
|
|
|
|
detailed.entry = *p_info;
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
//? Update cpu percent deque for process cpu graph
|
2021-07-29 23:40:56 +02:00
|
|
|
if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount;
|
2021-09-20 17:23:34 +02:00
|
|
|
detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll));
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front();
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
//? Process runtime
|
2021-08-17 22:33:21 +02:00
|
|
|
detailed.elapsed = sec_to_dhms(uptime - (detailed.entry.cpu_s / Shared::clkTck));
|
2021-07-26 01:06:34 +02:00
|
|
|
if (detailed.elapsed.size() > 8) detailed.elapsed.resize(detailed.elapsed.size() - 3);
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
//? Get parent process name
|
2021-08-17 22:33:21 +02:00
|
|
|
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;
|
|
|
|
}
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
//? Expand process status from single char to explanative string
|
|
|
|
detailed.status = (proc_states.contains(detailed.entry.state)) ? proc_states.at(detailed.entry.state) : "Unknown";
|
2021-06-30 22:28:12 +02:00
|
|
|
|
|
|
|
ifstream d_read;
|
2021-07-04 01:18:48 +02:00
|
|
|
string short_str;
|
2021-06-30 22:28:12 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//? Try to get RSS mem from proc/[pid]/smaps
|
|
|
|
detailed.memory.clear();
|
|
|
|
if (not detailed.skip_smaps and fs::exists(pid_path / "smaps")) {
|
|
|
|
d_read.open(pid_path / "smaps");
|
2021-08-03 23:47:46 +02:00
|
|
|
uint64_t rss = 0;
|
|
|
|
try {
|
|
|
|
while (d_read.good()) {
|
|
|
|
d_read.ignore(SSmax, 'R');
|
|
|
|
if (d_read.peek() == 's') {
|
|
|
|
d_read.ignore(SSmax, ':');
|
|
|
|
getline(d_read, short_str, 'k');
|
|
|
|
rss += stoull(short_str);
|
2021-07-26 01:06:34 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
if (rss == detailed.entry.mem >> 10)
|
|
|
|
detailed.skip_smaps = true;
|
|
|
|
else {
|
|
|
|
detailed.mem_bytes.push_back(rss << 10);
|
|
|
|
detailed.memory = floating_humanizer(rss, false, 1);
|
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
catch (const std::invalid_argument&) {}
|
|
|
|
catch (const std::out_of_range&) {}
|
2021-06-30 22:28:12 +02:00
|
|
|
d_read.close();
|
|
|
|
}
|
2021-07-26 01:06:34 +02:00
|
|
|
if (detailed.memory.empty()) {
|
|
|
|
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) {
|
2021-10-05 09:18:04 +02:00
|
|
|
detailed.first_mem = min((uint64_t)detailed.mem_bytes.back() * 2, Mem::get_totalMem());
|
2021-07-26 01:06:34 +02:00
|
|
|
redraw = true;
|
|
|
|
}
|
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
while (cmp_greater(detailed.mem_bytes.size(), width)) detailed.mem_bytes.pop_front();
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//? Get bytes read and written from proc/[pid]/io
|
|
|
|
if (fs::exists(pid_path / "io")) {
|
|
|
|
d_read.open(pid_path / "io");
|
2021-08-03 23:47:46 +02:00
|
|
|
try {
|
|
|
|
string name;
|
|
|
|
while (d_read.good()) {
|
|
|
|
getline(d_read, name, ':');
|
|
|
|
if (name.ends_with("read_bytes")) {
|
|
|
|
getline(d_read, short_str);
|
|
|
|
detailed.io_read = floating_humanizer(stoull(short_str));
|
|
|
|
}
|
|
|
|
else if (name.ends_with("write_bytes")) {
|
|
|
|
getline(d_read, short_str);
|
|
|
|
detailed.io_write = floating_humanizer(stoull(short_str));
|
|
|
|
break;
|
2021-07-04 01:18:48 +02:00
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
else
|
|
|
|
d_read.ignore(SSmax, '\n');
|
2021-07-04 01:18:48 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-03 23:47:46 +02:00
|
|
|
catch (const std::invalid_argument&) {}
|
|
|
|
catch (const std::out_of_range&) {}
|
2021-07-04 01:18:48 +02:00
|
|
|
d_read.close();
|
|
|
|
}
|
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//* Collects and sorts process information from /proc
|
2021-08-17 22:33:21 +02:00
|
|
|
auto collect(const bool no_update) -> vector<proc_info>& {
|
2021-07-21 03:17:34 +02:00
|
|
|
const auto& sorting = Config::getS("proc_sorting");
|
|
|
|
const auto& reverse = Config::getB("proc_reversed");
|
|
|
|
const auto& filter = Config::getS("proc_filter");
|
|
|
|
const auto& per_core = Config::getB("proc_per_core");
|
|
|
|
const auto& tree = Config::getB("proc_tree");
|
|
|
|
const auto& show_detailed = Config::getB("show_detailed");
|
|
|
|
const size_t detailed_pid = Config::getI("detailed_pid");
|
2021-09-01 21:40:13 +02:00
|
|
|
bool should_filter = current_filter != filter;
|
2021-08-22 16:04:01 +02:00
|
|
|
if (should_filter) current_filter = filter;
|
|
|
|
const bool sorted_change = (sorting != current_sort or reverse != current_rev or should_filter);
|
|
|
|
if (sorted_change) {
|
|
|
|
current_sort = sorting;
|
|
|
|
current_rev = reverse;
|
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
ifstream pread;
|
2021-06-25 23:58:19 +02:00
|
|
|
string long_string;
|
|
|
|
string short_str;
|
2021-08-17 22:33:21 +02:00
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
const double uptime = system_uptime();
|
2021-08-17 22:33:21 +02:00
|
|
|
|
2021-07-26 01:06:34 +02:00
|
|
|
const int cmult = (per_core) ? Shared::coreCount : 1;
|
2021-06-30 22:28:12 +02:00
|
|
|
bool got_detailed = false;
|
2021-08-11 23:21:33 +02:00
|
|
|
|
|
|
|
//* Use pids from last update if only changing filter, sorting or tree options
|
2021-08-17 22:33:21 +02:00
|
|
|
if (no_update and not current_procs.empty()) {
|
|
|
|
if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), current_procs);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
//* ---------------------------------------------Collection start----------------------------------------------
|
|
|
|
else {
|
2021-09-01 21:40:13 +02:00
|
|
|
should_filter = true;
|
|
|
|
|
2021-10-05 09:18:04 +02:00
|
|
|
auto totalMem = Mem::get_totalMem();
|
|
|
|
int totalMem_len = to_string(totalMem >> 10).size();
|
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Update uid_user map if /etc/passwd changed since last run
|
|
|
|
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != passwd_time) {
|
|
|
|
string r_uid, r_user;
|
|
|
|
passwd_time = fs::last_write_time(Shared::passwd_path);
|
|
|
|
uid_user.clear();
|
|
|
|
pread.open(Shared::passwd_path);
|
|
|
|
if (pread.good()) {
|
2021-10-15 08:32:37 +02:00
|
|
|
while (pread.good()) {
|
2021-08-11 23:21:33 +02:00
|
|
|
getline(pread, r_user, ':');
|
|
|
|
pread.ignore(SSmax, ':');
|
|
|
|
getline(pread, r_uid, ':');
|
|
|
|
uid_user[r_uid] = r_user;
|
2021-06-30 22:28:12 +02:00
|
|
|
pread.ignore(SSmax, '\n');
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
else {
|
|
|
|
Shared::passwd_path.clear();
|
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
pread.close();
|
|
|
|
}
|
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Get cpu total times from /proc/stat
|
|
|
|
cputimes = 0;
|
|
|
|
pread.open(Shared::procPath / "stat");
|
|
|
|
if (pread.good()) {
|
|
|
|
pread.ignore(SSmax, ' ');
|
|
|
|
for (uint64_t times; pread >> times; cputimes += times);
|
|
|
|
}
|
|
|
|
else throw std::runtime_error("Failure to read /proc/stat");
|
2021-08-12 22:25:18 +02:00
|
|
|
pread.close();
|
2021-08-11 23:21:33 +02:00
|
|
|
|
|
|
|
//? Iterate over all pids in /proc
|
2021-08-17 22:33:21 +02:00
|
|
|
vector<size_t> found;
|
2021-08-11 23:21:33 +02:00
|
|
|
for (const auto& d: fs::directory_iterator(Shared::procPath)) {
|
|
|
|
if (Runner::stopping)
|
2021-08-17 22:33:21 +02:00
|
|
|
return current_procs;
|
2021-08-11 23:21:33 +02:00
|
|
|
if (pread.is_open()) pread.close();
|
|
|
|
|
|
|
|
const string pid_str = d.path().filename();
|
|
|
|
if (not isdigit(pid_str[0])) continue;
|
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
const size_t pid = stoul(pid_str);
|
|
|
|
found.push_back(pid);
|
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
//? Check if pid already exists in current_procs
|
2021-08-17 22:33:21 +02:00
|
|
|
auto find_old = rng::find(current_procs, pid, &proc_info::pid);
|
|
|
|
bool no_cache = false;
|
|
|
|
if (find_old == current_procs.end()) {
|
|
|
|
current_procs.push_back({pid});
|
|
|
|
find_old = current_procs.end() - 1;
|
|
|
|
no_cache = true;
|
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
auto& new_proc = *find_old;
|
|
|
|
|
|
|
|
//? Get program name, command and username
|
|
|
|
if (no_cache) {
|
2021-08-11 23:21:33 +02:00
|
|
|
pread.open(d.path() / "comm");
|
|
|
|
if (not pread.good()) continue;
|
2021-08-17 22:33:21 +02:00
|
|
|
getline(pread, new_proc.name);
|
2021-08-11 23:21:33 +02:00
|
|
|
pread.close();
|
2021-08-22 16:04:01 +02:00
|
|
|
//? Check for whitespace characters in name and set offset to get correct fields from stat file
|
2021-08-17 22:33:21 +02:00
|
|
|
new_proc.name_offset = rng::count(new_proc.name, ' ');
|
2021-08-11 23:21:33 +02:00
|
|
|
|
|
|
|
pread.open(d.path() / "cmdline");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
long_string.clear();
|
2022-02-15 07:11:22 +01:00
|
|
|
while(getline(pread, long_string, '\0')) {
|
2022-02-17 22:28:10 +01:00
|
|
|
new_proc.cmd += long_string + ' ';
|
2022-02-15 07:11:22 +01:00
|
|
|
if (new_proc.cmd.size() > 1000) {
|
|
|
|
new_proc.cmd.resize(1000);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
pread.close();
|
2021-08-17 22:33:21 +02:00
|
|
|
if (not new_proc.cmd.empty()) new_proc.cmd.pop_back();
|
2021-08-11 23:21:33 +02:00
|
|
|
|
|
|
|
pread.open(d.path() / "status");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
string uid;
|
|
|
|
string line;
|
2021-10-15 08:32:37 +02:00
|
|
|
while (pread.good()) {
|
2021-08-11 23:21:33 +02:00
|
|
|
getline(pread, line, ':');
|
|
|
|
if (line == "Uid") {
|
|
|
|
pread.ignore();
|
|
|
|
getline(pread, uid, '\t');
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
pread.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pread.close();
|
2021-10-06 10:47:24 +02:00
|
|
|
if (uid_user.contains(uid)) {
|
|
|
|
new_proc.user = uid_user.at(uid);
|
|
|
|
}
|
|
|
|
else {
|
2021-10-17 01:45:26 +02:00
|
|
|
#if !(defined(STATIC_BUILD) && defined(__GLIBC__))
|
2021-10-06 10:47:24 +02:00
|
|
|
try {
|
|
|
|
struct passwd* udet;
|
|
|
|
udet = getpwuid(stoi(uid));
|
|
|
|
if (udet != NULL and udet->pw_name != NULL) {
|
|
|
|
new_proc.user = string(udet->pw_name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
new_proc.user = uid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (...) { new_proc.user = uid; }
|
|
|
|
#else
|
|
|
|
new_proc.user = uid;
|
|
|
|
#endif
|
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Parse /proc/[pid]/stat
|
|
|
|
pread.open(d.path() / "stat");
|
|
|
|
if (not pread.good()) continue;
|
2021-06-30 22:28:12 +02:00
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
const auto& offset = new_proc.name_offset;
|
2021-08-11 23:21:33 +02:00
|
|
|
short_str.clear();
|
2021-09-01 21:40:13 +02:00
|
|
|
int x = 0, next_x = 3;
|
2021-08-11 23:21:33 +02:00
|
|
|
uint64_t cpu_t = 0;
|
|
|
|
try {
|
2021-09-01 21:40:13 +02:00
|
|
|
for (;;) {
|
2021-10-15 08:32:37 +02:00
|
|
|
while (pread.good() and ++x < next_x + offset) pread.ignore(SSmax, ' ');
|
2021-09-01 21:40:13 +02:00
|
|
|
if (not pread.good()) break;
|
2021-10-15 08:32:37 +02:00
|
|
|
else getline(pread, short_str, ' ');
|
2021-08-11 23:21:33 +02:00
|
|
|
|
|
|
|
switch (x-offset) {
|
2021-08-15 23:20:55 +02:00
|
|
|
case 3: //? Process state
|
2021-08-11 23:21:33 +02:00
|
|
|
new_proc.state = short_str.at(0);
|
2021-09-01 21:40:13 +02:00
|
|
|
if (new_proc.ppid != 0) next_x = 14;
|
2021-08-11 23:21:33 +02:00
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 4: //? Parent pid
|
2021-08-11 23:21:33 +02:00
|
|
|
new_proc.ppid = stoull(short_str);
|
|
|
|
next_x = 14;
|
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 14: //? Process utime
|
2021-08-11 23:21:33 +02:00
|
|
|
cpu_t = stoull(short_str);
|
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 15: //? Process stime
|
2021-08-11 23:21:33 +02:00
|
|
|
cpu_t += stoull(short_str);
|
|
|
|
next_x = 19;
|
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 19: //? Nice value
|
2021-08-11 23:21:33 +02:00
|
|
|
new_proc.p_nice = stoull(short_str);
|
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 20: //? Number of threads
|
2021-08-11 23:21:33 +02:00
|
|
|
new_proc.threads = stoull(short_str);
|
2021-08-17 22:33:21 +02:00
|
|
|
if (new_proc.cpu_s == 0) {
|
2021-08-11 23:21:33 +02:00
|
|
|
next_x = 22;
|
2021-08-17 22:33:21 +02:00
|
|
|
new_proc.cpu_t = cpu_t;
|
2021-08-11 23:21:33 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
next_x = 24;
|
|
|
|
continue;
|
2021-08-17 22:33:21 +02:00
|
|
|
case 22: //? Get cpu seconds if missing
|
|
|
|
new_proc.cpu_s = stoull(short_str);
|
2021-07-15 23:49:16 +02:00
|
|
|
next_x = 24;
|
2021-08-11 23:21:33 +02:00
|
|
|
continue;
|
2021-08-15 23:20:55 +02:00
|
|
|
case 24: //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x)
|
2021-10-05 09:18:04 +02:00
|
|
|
if (cmp_greater(short_str.size(), totalMem_len))
|
|
|
|
new_proc.mem = totalMem;
|
2021-09-21 21:24:58 +02:00
|
|
|
else
|
|
|
|
new_proc.mem = stoull(short_str) * Shared::pageSize;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-09-01 21:40:13 +02:00
|
|
|
break;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
catch (const std::invalid_argument&) { continue; }
|
|
|
|
catch (const std::out_of_range&) { continue; }
|
2021-09-01 21:40:13 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
pread.close();
|
2021-06-27 01:19:57 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
if (x-offset < 24) continue;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-09-21 21:24:58 +02:00
|
|
|
//? Get RSS memory from /proc/[pid]/statm if value from /proc/[pid]/stat looks wrong
|
2021-10-05 09:18:04 +02:00
|
|
|
if (new_proc.mem >= totalMem) {
|
2021-09-21 21:24:58 +02:00
|
|
|
pread.open(d.path() / "statm");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
pread.ignore(SSmax, ' ');
|
|
|
|
pread >> new_proc.mem;
|
|
|
|
new_proc.mem *= Shared::pageSize;
|
|
|
|
pread.close();
|
|
|
|
}
|
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Process cpu usage since last update
|
2021-09-21 17:21:42 +02:00
|
|
|
new_proc.cpu_p = clamp(round(cmult * 1000 * (cpu_t - new_proc.cpu_t) / max((uint64_t)1, cputimes - old_cputimes)) / 10.0, 0.0, 100.0 * Shared::coreCount);
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Process cumulative cpu usage since process start
|
2021-08-17 22:33:21 +02:00
|
|
|
new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - new_proc.cpu_s);
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-09-17 14:25:54 +02:00
|
|
|
//? Update cached value with latest cpu times
|
2021-08-17 22:33:21 +02:00
|
|
|
new_proc.cpu_t = cpu_t;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
|
|
|
|
got_detailed = true;
|
|
|
|
}
|
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
//? Clear dead processes from current_procs
|
2021-08-17 22:33:21 +02:00
|
|
|
auto eraser = rng::remove_if(current_procs, [&](const auto& element){ return not v_contains(found, element.pid); });
|
|
|
|
current_procs.erase(eraser.begin(), eraser.end());
|
2021-06-30 22:28:12 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
//? Update the details info box for process if active
|
|
|
|
if (show_detailed and got_detailed) {
|
2021-08-17 22:33:21 +02:00
|
|
|
_collect_details(detailed_pid, round(uptime), current_procs);
|
2021-08-11 23:21:33 +02:00
|
|
|
}
|
|
|
|
else if (show_detailed and not got_detailed and detailed.status != "Dead") {
|
|
|
|
detailed.status = "Dead";
|
|
|
|
redraw = true;
|
|
|
|
}
|
2021-07-26 01:06:34 +02:00
|
|
|
|
2021-08-11 23:21:33 +02:00
|
|
|
old_cputimes = cputimes;
|
2021-07-04 01:18:48 +02:00
|
|
|
}
|
2021-08-11 23:21:33 +02:00
|
|
|
//* ---------------------------------------------Collection done-----------------------------------------------
|
2021-07-26 01:06:34 +02:00
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
//* Sort processes
|
2021-08-22 16:04:01 +02:00
|
|
|
if (sorted_change or not no_update) {
|
2021-12-27 10:55:17 +01:00
|
|
|
if (reverse) {
|
|
|
|
switch (v_index(sort_vector, sorting)) {
|
|
|
|
case 0: rng::stable_sort(current_procs, rng::less{}, &proc_info::pid); break;
|
|
|
|
case 1: rng::stable_sort(current_procs, rng::less{}, &proc_info::name); break;
|
|
|
|
case 2: rng::stable_sort(current_procs, rng::less{}, &proc_info::cmd); break;
|
|
|
|
case 3: rng::stable_sort(current_procs, rng::less{}, &proc_info::threads); break;
|
|
|
|
case 4: rng::stable_sort(current_procs, rng::less{}, &proc_info::user); break;
|
|
|
|
case 5: rng::stable_sort(current_procs, rng::less{}, &proc_info::mem); break;
|
|
|
|
case 6: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_p); break;
|
|
|
|
case 7: rng::stable_sort(current_procs, rng::less{}, &proc_info::cpu_c); break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (v_index(sort_vector, sorting)) {
|
|
|
|
case 0: rng::stable_sort(current_procs, rng::greater{}, &proc_info::pid); break;
|
|
|
|
case 1: rng::stable_sort(current_procs, rng::greater{}, &proc_info::name); break;
|
|
|
|
case 2: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cmd); break;
|
|
|
|
case 3: rng::stable_sort(current_procs, rng::greater{}, &proc_info::threads); break;
|
|
|
|
case 4: rng::stable_sort(current_procs, rng::greater{}, &proc_info::user); break;
|
|
|
|
case 5: rng::stable_sort(current_procs, rng::greater{}, &proc_info::mem); break;
|
|
|
|
case 6: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_p); break;
|
|
|
|
case 7: rng::stable_sort(current_procs, rng::greater{}, &proc_info::cpu_c); break;
|
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
|
|
|
|
if (not tree and not reverse and sorting == "cpu lazy") {
|
|
|
|
double max = 10.0, target = 30.0;
|
|
|
|
for (size_t i = 0, x = 0, offset = 0; i < current_procs.size(); i++) {
|
|
|
|
if (i <= 5 and current_procs.at(i).cpu_p > max)
|
|
|
|
max = current_procs.at(i).cpu_p;
|
|
|
|
else if (i == 6)
|
|
|
|
target = (max > 30.0) ? max : 10.0;
|
|
|
|
if (i == offset and current_procs.at(i).cpu_p > 30.0)
|
|
|
|
offset++;
|
|
|
|
else if (current_procs.at(i).cpu_p > target) {
|
|
|
|
rotate(current_procs.begin() + offset, current_procs.begin() + i, current_procs.begin() + i + 1);
|
|
|
|
if (++x > 10) break;
|
|
|
|
}
|
2021-07-21 03:17:34 +02:00
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
//* Match filter if defined
|
2021-08-22 16:04:01 +02:00
|
|
|
if (should_filter) {
|
|
|
|
filter_found = 0;
|
|
|
|
for (auto& p : current_procs) {
|
|
|
|
if (not tree and not filter.empty()) {
|
2022-04-30 19:08:27 +02:00
|
|
|
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)) {
|
2021-08-17 22:33:21 +02:00
|
|
|
p.filtered = true;
|
|
|
|
filter_found++;
|
2021-08-22 16:04:01 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
p.filtered = false;
|
2021-08-17 22:33:21 +02:00
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
p.filtered = false;
|
2021-08-17 22:33:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
//* Generate tree view if enabled
|
2021-08-22 16:04:01 +02:00
|
|
|
if (tree and (not no_update or should_filter or sorted_change)) {
|
2021-08-17 22:33:21 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
collapse = expand = -1;
|
2021-07-29 23:40:56 +02:00
|
|
|
}
|
2021-08-22 16:04:01 +02:00
|
|
|
if (should_filter or not filter.empty()) filter_found = 0;
|
2021-07-29 23:40:56 +02:00
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
vector<std::reference_wrapper<proc_info>> tree_procs;
|
2021-08-17 22:33:21 +02:00
|
|
|
tree_procs.reserve(current_procs.size());
|
2021-06-20 22:07:04 +02:00
|
|
|
|
|
|
|
//? Stable sort to retain selected sorting among processes with the same parent
|
2021-08-17 22:33:21 +02:00
|
|
|
rng::stable_sort(current_procs, rng::less{}, &proc_info::ppid);
|
2021-06-20 22:07:04 +02:00
|
|
|
|
2021-06-27 01:19:57 +02:00
|
|
|
//? Start recursive iteration over processes with the lowest shared parent pids
|
2021-08-22 16:04:01 +02:00
|
|
|
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);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-07-18 15:44:32 +02:00
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
//? Final sort based on tree index
|
2021-12-27 10:55:17 +01:00
|
|
|
rng::stable_sort(current_procs, rng::less{}, &proc_info::tree_index);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
|
2021-08-22 16:04:01 +02:00
|
|
|
numpids = (int)current_procs.size() - filter_found;
|
2021-07-26 01:06:34 +02:00
|
|
|
|
2021-08-17 22:33:21 +02:00
|
|
|
return current_procs;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-10 20:20:33 +02:00
|
|
|
namespace Tools {
|
|
|
|
double system_uptime() {
|
|
|
|
string upstr;
|
|
|
|
ifstream pread(Shared::procPath / "uptime");
|
2021-09-26 01:03:57 +02:00
|
|
|
if (pread.good()) {
|
|
|
|
try {
|
|
|
|
getline(pread, upstr, ' ');
|
|
|
|
pread.close();
|
|
|
|
return stod(upstr);
|
|
|
|
}
|
|
|
|
catch (const std::invalid_argument&) {}
|
|
|
|
catch (const std::out_of_range&) {}
|
|
|
|
}
|
|
|
|
throw std::runtime_error("Failed get uptime from from " + (string)Shared::procPath + "/uptime");
|
2021-08-10 20:20:33 +02:00
|
|
|
}
|
2021-11-17 00:08:05 +01:00
|
|
|
}
|