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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if defined(__linux__)
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include <ranges>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cmath>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
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
|
|
|
|
|
|
|
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
|
2021-07-04 01:18:48 +02:00
|
|
|
std::round, std::string_literals::operator""s;
|
2021-06-19 14:57:27 +02:00
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace rng = std::ranges;
|
|
|
|
using namespace Tools;
|
|
|
|
|
|
|
|
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
|
|
|
|
|
|
|
namespace Tools {
|
2021-07-21 03:17:34 +02:00
|
|
|
double system_uptime() {
|
2021-06-19 14:57:27 +02:00
|
|
|
string upstr;
|
|
|
|
ifstream pread("/proc/uptime");
|
|
|
|
getline(pread, upstr, ' ');
|
|
|
|
pread.close();
|
|
|
|
return stod(upstr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 23:58:19 +02:00
|
|
|
namespace Shared {
|
|
|
|
|
|
|
|
fs::path proc_path;
|
|
|
|
fs::path passwd_path;
|
|
|
|
fs::file_time_type passwd_time;
|
|
|
|
long page_size;
|
|
|
|
long clk_tck;
|
|
|
|
|
2021-07-21 03:17:34 +02:00
|
|
|
void init() {
|
2021-06-25 23:58:19 +02:00
|
|
|
proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
|
|
|
|
if (proc_path.empty()) {
|
2021-07-21 03:17:34 +02:00
|
|
|
Global::exit_error_msg = "Proc filesystem not found or no permission to read from it!";
|
|
|
|
clean_quit(1);
|
2021-06-25 23:58:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
|
|
|
|
if (passwd_path.empty()) Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
|
|
|
|
|
|
|
|
page_size = sysconf(_SC_PAGE_SIZE);
|
|
|
|
if (page_size <= 0) {
|
|
|
|
page_size = 4096;
|
|
|
|
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
|
|
|
|
}
|
|
|
|
|
|
|
|
clk_tck = sysconf(_SC_CLK_TCK);
|
|
|
|
if (clk_tck <= 0) {
|
|
|
|
clk_tck = 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-07-04 22:02:31 +02:00
|
|
|
namespace Cpu {
|
|
|
|
bool got_sensors = false;
|
|
|
|
string cpuName = "";
|
2021-07-18 15:44:32 +02:00
|
|
|
|
|
|
|
cpu_info current_cpu;
|
|
|
|
|
|
|
|
cpu_info& collect(const bool return_last) {
|
|
|
|
(void)return_last;
|
|
|
|
return current_cpu;
|
|
|
|
}
|
2021-07-04 22:02:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace Mem {
|
|
|
|
bool has_swap = false;
|
2021-07-18 15:44:32 +02:00
|
|
|
|
|
|
|
mem_info current_mem;
|
|
|
|
|
|
|
|
mem_info& collect(const bool return_last) {
|
|
|
|
(void)return_last;
|
|
|
|
return current_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Net {
|
|
|
|
net_info current_net;
|
|
|
|
|
|
|
|
net_info& collect(const bool return_last) {
|
|
|
|
(void)return_last;
|
|
|
|
return current_net;
|
|
|
|
}
|
2021-07-04 22:02:31 +02:00
|
|
|
}
|
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
namespace Proc {
|
|
|
|
namespace {
|
|
|
|
struct p_cache {
|
|
|
|
string name, cmd, user;
|
2021-06-30 22:28:12 +02:00
|
|
|
size_t name_offset;
|
2021-06-19 14:57:27 +02:00
|
|
|
uint64_t cpu_t = 0, cpu_s = 0;
|
|
|
|
string prefix = "";
|
|
|
|
size_t depth = 0;
|
|
|
|
bool collapsed = false;
|
|
|
|
};
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
vector<proc_info> current_procs;
|
2021-06-27 22:13:32 +02:00
|
|
|
unordered_flat_map<size_t, p_cache> cache;
|
2021-06-19 14:57:27 +02:00
|
|
|
unordered_flat_map<string, string> uid_user;
|
|
|
|
|
2021-06-27 22:13:32 +02:00
|
|
|
int counter = 0;
|
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-07-15 23:49:16 +02:00
|
|
|
size_t reserve_pids = 500;
|
|
|
|
bool tree_state = false;
|
2021-06-19 14:57:27 +02:00
|
|
|
vector<string> sort_vector = {
|
|
|
|
"pid",
|
|
|
|
"name",
|
|
|
|
"command",
|
|
|
|
"threads",
|
|
|
|
"user",
|
|
|
|
"memory",
|
|
|
|
"cpu direct",
|
|
|
|
"cpu lazy",
|
|
|
|
};
|
2021-07-04 01:18:48 +02:00
|
|
|
unordered_flat_map<char, string> proc_states = {
|
|
|
|
{'R', "Running"},
|
|
|
|
{'S', "Sleeping"},
|
|
|
|
{'D', "Waiting"},
|
|
|
|
{'Z', "Zombie"},
|
|
|
|
{'T', "Stopped"},
|
|
|
|
{'t', "Tracing"},
|
|
|
|
{'X', "Dead"},
|
|
|
|
{'x', "Dead"},
|
|
|
|
{'K', "Wakekill"},
|
|
|
|
{'W', "Unknown"},
|
|
|
|
{'P', "Parked"}
|
|
|
|
};
|
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-07-21 03:17:34 +02:00
|
|
|
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false) {
|
2021-07-18 15:44:32 +02:00
|
|
|
if (Runner::stopping) return;
|
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
|
|
|
|
if (not filter.empty() and not found) {
|
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;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
found = true;
|
|
|
|
cur_depth = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 23:49:16 +02:00
|
|
|
if (not collapsed and not filtering) {
|
2021-06-19 14:57:27 +02:00
|
|
|
out_procs.push_back(cur_proc);
|
2021-07-15 23:49:16 +02:00
|
|
|
if (auto& cmdline = cache.at(cur_proc.pid).cmd; not cmdline.empty() and not cmdline.starts_with("(")) {
|
2021-07-21 03:17:34 +02:00
|
|
|
std::string_view cmd_view = cmdline;
|
|
|
|
cmd_view = cmd_view.substr(0, std::min(cmd_view.find(' '), cmd_view.size()));
|
|
|
|
cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
|
|
|
|
if (cmd_view == cur_proc.name)
|
2021-07-15 23:49:16 +02:00
|
|
|
cmdline.clear();
|
|
|
|
else
|
2021-07-21 03:17:34 +02:00
|
|
|
cmdline = '(' + (string)cmd_view + ')';
|
2021-07-15 23:49:16 +02:00
|
|
|
out_procs.back().cmd = cmdline;
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 22:07:04 +02:00
|
|
|
|
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-06-27 22:13:32 +02:00
|
|
|
if (collapsed and not filtering) {
|
2021-06-20 22:07:04 +02:00
|
|
|
out_procs.back().cpu_p += p.cpu_p;
|
|
|
|
out_procs.back().mem += p.mem;
|
|
|
|
out_procs.back().threads += p.threads;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-27 01:19:57 +02:00
|
|
|
else children++;
|
2021-06-30 22:28:12 +02:00
|
|
|
_tree_gen(p, in_procs, out_procs, cur_depth + 1, (collapsed ? true : cache.at(cur_proc.pid).collapsed), filter, found);
|
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-07-15 23:49:16 +02:00
|
|
|
if (out_procs.size() > cur_pos + 1 and not out_procs.back().prefix.ends_with("]─"))
|
2021-06-27 01:19:57 +02:00
|
|
|
out_procs.back().prefix.replace(out_procs.back().prefix.size() - 8, 8, " └─ ");
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-15 23:49:16 +02:00
|
|
|
out_procs.at(cur_pos).prefix = " │ "s * cur_depth + (children > 0 ? (cache.at(cur_proc.pid).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-21 03:17:34 +02:00
|
|
|
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs, const bool is_filtered) {
|
2021-07-04 01:18:48 +02:00
|
|
|
fs::path pid_path = Shared::proc_path / std::to_string(pid);
|
|
|
|
|
|
|
|
if (pid != detailed.last_pid) {
|
|
|
|
detailed.last_pid = pid;
|
|
|
|
detailed.cpu_percent.clear();
|
|
|
|
detailed.parent.clear();
|
|
|
|
detailed.io_read.clear();
|
|
|
|
detailed.io_write.clear();
|
|
|
|
detailed.skip_smaps = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//? Copy proc_info for process from proc vector
|
|
|
|
auto p = rng::find(procs, pid, &proc_info::pid);
|
|
|
|
detailed.entry = *p;
|
|
|
|
|
|
|
|
//? Update cpu percent deque for process cpu graph
|
|
|
|
detailed.cpu_percent.push_back(round(detailed.entry.cpu_c));
|
2021-07-05 22:18:58 +02:00
|
|
|
while (detailed.cpu_percent.size() > (size_t)Term::width) detailed.cpu_percent.pop_front();
|
2021-07-04 01:18:48 +02:00
|
|
|
|
|
|
|
//? Process runtime
|
|
|
|
detailed.elapsed = sec_to_dhms(uptime - (cache.at(pid).cpu_s / Shared::clk_tck));
|
|
|
|
|
|
|
|
//? Get parent process name
|
|
|
|
if (detailed.parent.empty() and cache.contains(detailed.entry.ppid)) detailed.parent = cache.at(detailed.entry.ppid).name;
|
|
|
|
|
|
|
|
//? 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-06-30 22:28:12 +02:00
|
|
|
if (d_read.good()) {
|
|
|
|
uint64_t rss = 0;
|
|
|
|
try {
|
|
|
|
while (not d_read.eof()) {
|
|
|
|
d_read.ignore(SSmax, 'R');
|
|
|
|
if (d_read.peek() == 's') {
|
|
|
|
d_read.ignore(SSmax, ':');
|
2021-07-04 01:18:48 +02:00
|
|
|
getline(d_read, short_str, 'k');
|
|
|
|
rss += stoull(short_str);
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-04 01:18:48 +02:00
|
|
|
if (rss == detailed.entry.mem >> 10)
|
|
|
|
detailed.skip_smaps = true;
|
|
|
|
else
|
|
|
|
detailed.memory = floating_humanizer(rss, false, 1);
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
2021-07-21 03:17:34 +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-04 01:18:48 +02:00
|
|
|
if (detailed.memory.empty()) detailed.memory = floating_humanizer(detailed.entry.mem, false);
|
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");
|
|
|
|
if (d_read.good()) {
|
|
|
|
try {
|
|
|
|
string name;
|
|
|
|
while (not d_read.eof()) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
d_read.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
2021-07-21 03:17:34 +02:00
|
|
|
catch (const std::invalid_argument&) {}
|
|
|
|
catch (const std::out_of_range&) {}
|
2021-07-04 01:18:48 +02:00
|
|
|
}
|
|
|
|
d_read.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_filtered) procs.erase(p);
|
|
|
|
|
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//* Collects and sorts process information from /proc
|
2021-07-21 03:17:34 +02:00
|
|
|
vector<proc_info>& collect(const bool return_last) {
|
2021-07-04 01:18:48 +02:00
|
|
|
if (return_last) return current_procs;
|
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");
|
2021-07-15 23:49:16 +02:00
|
|
|
if (tree_state != tree) {
|
|
|
|
cache.clear();
|
|
|
|
tree_state = tree;
|
|
|
|
}
|
2021-07-21 03:17:34 +02:00
|
|
|
const auto& show_detailed = Config::getB("show_detailed");
|
|
|
|
const size_t detailed_pid = Config::getI("detailed_pid");
|
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-07-21 03:17:34 +02:00
|
|
|
const double uptime = system_uptime();
|
2021-06-19 14:57:27 +02:00
|
|
|
vector<proc_info> procs;
|
2021-07-15 23:49:16 +02:00
|
|
|
procs.reserve(reserve_pids + 10);
|
2021-06-19 14:57:27 +02:00
|
|
|
int npids = 0;
|
2021-07-21 03:17:34 +02:00
|
|
|
const int cmult = (per_core) ? Global::coreCount : 1;
|
2021-06-30 22:28:12 +02:00
|
|
|
bool got_detailed = false;
|
2021-07-04 01:18:48 +02:00
|
|
|
bool detailed_filtered = false;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//? Update uid_user map if /etc/passwd changed since last run
|
2021-06-25 23:58:19 +02:00
|
|
|
if (not Shared::passwd_path.empty() and fs::last_write_time(Shared::passwd_path) != Shared::passwd_time) {
|
2021-06-19 14:57:27 +02:00
|
|
|
string r_uid, r_user;
|
2021-06-25 23:58:19 +02:00
|
|
|
Shared::passwd_time = fs::last_write_time(Shared::passwd_path);
|
2021-06-19 14:57:27 +02:00
|
|
|
uid_user.clear();
|
2021-06-25 23:58:19 +02:00
|
|
|
pread.open(Shared::passwd_path);
|
2021-06-19 14:57:27 +02:00
|
|
|
if (pread.good()) {
|
2021-07-21 03:17:34 +02:00
|
|
|
while (not pread.eof()) {
|
2021-06-19 14:57:27 +02:00
|
|
|
getline(pread, r_user, ':');
|
|
|
|
pread.ignore(SSmax, ':');
|
|
|
|
getline(pread, r_uid, ':');
|
|
|
|
uid_user[r_uid] = r_user;
|
|
|
|
pread.ignore(SSmax, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pread.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
//* Get cpu total times from /proc/stat
|
|
|
|
uint64_t cputimes = 0;
|
2021-06-25 23:58:19 +02:00
|
|
|
pread.open(Shared::proc_path / "stat");
|
2021-06-19 14:57:27 +02:00
|
|
|
if (pread.good()) {
|
|
|
|
pread.ignore(SSmax, ' ');
|
|
|
|
for (uint64_t times; pread >> times; cputimes += times);
|
|
|
|
pread.close();
|
|
|
|
}
|
|
|
|
else return current_procs;
|
|
|
|
|
|
|
|
//* Iterate over all pids in /proc
|
2021-07-21 03:17:34 +02:00
|
|
|
for (const auto& d: fs::directory_iterator(Shared::proc_path)) {
|
2021-07-18 15:44:32 +02:00
|
|
|
if (Runner::stopping)
|
2021-06-19 14:57:27 +02:00
|
|
|
return current_procs;
|
2021-07-18 15:44:32 +02:00
|
|
|
if (pread.is_open()) pread.close();
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-21 03:17:34 +02:00
|
|
|
const string pid_str = d.path().filename();
|
2021-06-30 22:28:12 +02:00
|
|
|
if (not isdigit(pid_str[0])) continue;
|
|
|
|
|
|
|
|
npids++;
|
|
|
|
proc_info new_proc (stoul(pid_str));
|
|
|
|
|
|
|
|
//* Cache program name, command and username
|
|
|
|
if (not cache.contains(new_proc.pid)) {
|
|
|
|
string name, cmd, user;
|
|
|
|
pread.open(d.path() / "comm");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
getline(pread, name);
|
|
|
|
pread.close();
|
|
|
|
size_t name_offset = rng::count(name, ' ');
|
|
|
|
|
|
|
|
pread.open(d.path() / "cmdline");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
long_string.clear();
|
|
|
|
while(getline(pread, long_string, '\0')) cmd += long_string + ' ';
|
|
|
|
pread.close();
|
|
|
|
if (not cmd.empty()) cmd.pop_back();
|
|
|
|
|
|
|
|
pread.open(d.path() / "status");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
string uid;
|
|
|
|
string line;
|
2021-07-21 03:17:34 +02:00
|
|
|
while (not pread.eof()) {
|
2021-06-30 22:28:12 +02:00
|
|
|
getline(pread, line, ':');
|
|
|
|
if (line == "Uid") {
|
|
|
|
pread.ignore();
|
|
|
|
getline(pread, uid, '\t');
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
pread.ignore(SSmax, '\n');
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
|
|
|
pread.close();
|
|
|
|
user = (uid_user.contains(uid)) ? uid_user.at(uid) : uid;
|
|
|
|
|
|
|
|
cache[new_proc.pid] = {name, cmd, user, name_offset};
|
|
|
|
}
|
|
|
|
|
|
|
|
//* Match filter if defined
|
|
|
|
if (not tree and not filter.empty()
|
2021-07-04 22:02:31 +02:00
|
|
|
and not s_contains(pid_str, filter)
|
|
|
|
and not s_contains(cache[new_proc.pid].name, filter)
|
|
|
|
and not s_contains(cache[new_proc.pid].cmd, filter)
|
|
|
|
and not s_contains(cache[new_proc.pid].user, filter)) {
|
2021-07-04 01:18:48 +02:00
|
|
|
if (show_detailed and new_proc.pid == detailed_pid)
|
|
|
|
detailed_filtered = true;
|
|
|
|
else
|
|
|
|
continue;
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
|
|
|
new_proc.name = cache[new_proc.pid].name;
|
|
|
|
new_proc.cmd = cache[new_proc.pid].cmd;
|
|
|
|
new_proc.user = cache[new_proc.pid].user;
|
|
|
|
|
|
|
|
//* Parse /proc/[pid]/stat
|
|
|
|
pread.open(d.path() / "stat");
|
|
|
|
if (not pread.good()) continue;
|
|
|
|
|
|
|
|
//? Check cached value for whitespace characters in name and set offset to get correct fields from stat file
|
|
|
|
size_t& offset = cache.at(new_proc.pid).name_offset;
|
|
|
|
short_str.clear();
|
|
|
|
size_t x = 0, next_x = 3;
|
|
|
|
uint64_t cpu_t = 0;
|
|
|
|
try {
|
|
|
|
for (;;) {
|
|
|
|
while (++x - offset < next_x) {
|
|
|
|
pread.ignore(SSmax, ' ');
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
|
|
|
|
getline(pread, short_str, ' ');
|
|
|
|
|
|
|
|
switch (x-offset) {
|
|
|
|
case 3: { //? Process state
|
|
|
|
new_proc.state = short_str[0];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 4: { //? Parent pid
|
|
|
|
new_proc.ppid = stoull(short_str);
|
|
|
|
next_x = 14;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 14: { //? Process utime
|
|
|
|
cpu_t = stoull(short_str);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 15: { //? Process stime
|
|
|
|
cpu_t += stoull(short_str);
|
|
|
|
next_x = 19;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 19: { //? Nice value
|
|
|
|
new_proc.p_nice = stoull(short_str);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 20: { //? Number of threads
|
|
|
|
new_proc.threads = stoull(short_str);
|
2021-07-15 23:49:16 +02:00
|
|
|
if (cache[new_proc.pid].cpu_s == 0) {
|
|
|
|
next_x = 22;
|
|
|
|
cache[new_proc.pid].cpu_t = cpu_t;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
next_x = 24;
|
2021-06-30 22:28:12 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 22: { //? Save cpu seconds to cache if missing
|
|
|
|
cache[new_proc.pid].cpu_s = stoull(short_str);
|
|
|
|
next_x = 24;
|
|
|
|
continue;
|
|
|
|
}
|
2021-07-04 22:02:31 +02:00
|
|
|
case 24: { //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x)
|
2021-06-30 22:28:12 +02:00
|
|
|
new_proc.mem = stoull(short_str) * Shared::page_size;
|
|
|
|
next_x = 40;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case 40: { //? CPU number last executed on
|
|
|
|
new_proc.cpu_n = stoull(short_str);
|
|
|
|
goto stat_loop_done;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
}
|
2021-07-18 18:04:49 +02:00
|
|
|
catch (const std::invalid_argument&) { continue; }
|
|
|
|
catch (const std::out_of_range&) { continue; }
|
2021-06-27 01:19:57 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
stat_loop_done:
|
|
|
|
pread.close();
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
if (x-offset < 24) continue;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
//? Process cpu usage since last update
|
|
|
|
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
//? Process cumulative cpu usage since process start
|
|
|
|
new_proc.cpu_c = ((double)cpu_t / Shared::clk_tck) / (uptime - (cache[new_proc.pid].cpu_s / Shared::clk_tck));
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-06-30 22:28:12 +02:00
|
|
|
//? Update cache with latest cpu times
|
|
|
|
cache[new_proc.pid].cpu_t = cpu_t;
|
2021-06-19 14:57:27 +02:00
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) {
|
2021-06-30 22:28:12 +02:00
|
|
|
got_detailed = true;
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-30 22:28:12 +02:00
|
|
|
|
|
|
|
//? Push process to vector
|
|
|
|
procs.push_back(new_proc);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-04 01:18:48 +02:00
|
|
|
//* Update the details info box for process if active
|
|
|
|
if (show_detailed and got_detailed) {
|
|
|
|
_collect_details(detailed_pid, round(uptime), procs, detailed_filtered);
|
|
|
|
}
|
|
|
|
else if (show_detailed and not got_detailed) {
|
|
|
|
detailed.status = "Dead";
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//* Sort processes
|
2021-07-21 03:17:34 +02:00
|
|
|
const auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); };
|
2021-06-20 00:04:02 +02:00
|
|
|
switch (v_index(sort_vector, sorting)) {
|
2021-07-04 22:02:31 +02:00
|
|
|
case 0: { rng::sort(procs, cmp, &proc_info::pid); break; }
|
|
|
|
case 1: { rng::sort(procs, cmp, &proc_info::name); break; }
|
|
|
|
case 2: { rng::sort(procs, cmp, &proc_info::cmd); break; }
|
2021-06-20 00:04:02 +02:00
|
|
|
case 3: { rng::sort(procs, cmp, &proc_info::threads); break; }
|
2021-07-04 22:02:31 +02:00
|
|
|
case 4: { rng::sort(procs, cmp, &proc_info::user); break; }
|
|
|
|
case 5: { rng::sort(procs, cmp, &proc_info::mem); break; }
|
|
|
|
case 6: { rng::sort(procs, cmp, &proc_info::cpu_p); break; }
|
|
|
|
case 7: { rng::sort(procs, cmp, &proc_info::cpu_c); break; }
|
2021-06-20 00:04:02 +02:00
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
|
|
|
|
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
|
2021-06-27 01:19:57 +02:00
|
|
|
if (not tree and not reverse and sorting == "cpu lazy") {
|
2021-06-19 14:57:27 +02:00
|
|
|
double max = 10.0, target = 30.0;
|
2021-07-21 03:17:34 +02:00
|
|
|
for (size_t i = 0, x = 0, offset = 0; i < procs.size(); i++) {
|
2021-06-21 22:52:55 +02:00
|
|
|
if (i <= 5 and procs[i].cpu_p > max)
|
2021-06-19 14:57:27 +02:00
|
|
|
max = procs[i].cpu_p;
|
|
|
|
else if (i == 6)
|
|
|
|
target = (max > 30.0) ? max : 10.0;
|
2021-06-21 22:52:55 +02:00
|
|
|
if (i == offset and procs[i].cpu_p > 30.0)
|
2021-06-19 14:57:27 +02:00
|
|
|
offset++;
|
2021-07-21 03:17:34 +02:00
|
|
|
else if (procs[i].cpu_p > target) {
|
2021-06-25 23:58:19 +02:00
|
|
|
rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
|
2021-07-21 03:17:34 +02:00
|
|
|
if (++x > 10) break;
|
|
|
|
}
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//* Generate tree view if enabled
|
|
|
|
if (tree) {
|
|
|
|
vector<proc_info> tree_procs;
|
2021-07-04 22:02:31 +02:00
|
|
|
tree_procs.reserve(procs.size());
|
2021-06-20 22:07:04 +02:00
|
|
|
|
|
|
|
//? Stable sort to retain selected sorting among processes with the same parent
|
|
|
|
rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
|
|
|
|
|
2021-06-27 01:19:57 +02:00
|
|
|
//? Start recursive iteration over processes with the lowest shared parent pids
|
2021-07-21 03:17:34 +02:00
|
|
|
for (const auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
|
2021-06-30 22:28:12 +02:00
|
|
|
_tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-07-18 15:44:32 +02:00
|
|
|
|
|
|
|
if (Runner::stopping) return current_procs;
|
2021-07-21 03:17:34 +02:00
|
|
|
procs = std::move(tree_procs);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//* Clear dead processes from cache at a regular interval
|
2021-06-21 22:52:55 +02:00
|
|
|
if (++counter >= 10000 or ((int)cache.size() > npids + 100)) {
|
2021-06-19 14:57:27 +02:00
|
|
|
counter = 0;
|
2021-06-27 22:13:32 +02:00
|
|
|
unordered_flat_map<size_t, p_cache> r_cache;
|
2021-06-27 01:19:57 +02:00
|
|
|
r_cache.reserve(procs.size());
|
2021-07-21 03:17:34 +02:00
|
|
|
rng::for_each(procs, [&r_cache](const auto &p) {
|
2021-06-27 01:19:57 +02:00
|
|
|
if (cache.contains(p.pid))
|
|
|
|
r_cache[p.pid] = cache.at(p.pid);
|
|
|
|
});
|
2021-07-21 03:17:34 +02:00
|
|
|
cache = std::move(r_cache);
|
2021-06-19 14:57:27 +02:00
|
|
|
}
|
2021-06-20 22:07:04 +02:00
|
|
|
|
2021-06-19 14:57:27 +02:00
|
|
|
old_cputimes = cputimes;
|
2021-07-15 23:49:16 +02:00
|
|
|
numpids = (int)procs.size();
|
|
|
|
reserve_pids = npids;
|
2021-07-21 03:17:34 +02:00
|
|
|
current_procs = std::move(procs);
|
2021-06-19 14:57:27 +02:00
|
|
|
return current_procs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|