|
|
@@ -2,11 +2,16 @@
|
|
|
|
|
|
#include <algorithm>
|
|
|
#include <cctype>
|
|
|
+#include <chrono>
|
|
|
+#include <condition_variable>
|
|
|
#include <cstdlib>
|
|
|
#include <cstring>
|
|
|
#include <dirent.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <fcntl.h>
|
|
|
#include <fstream>
|
|
|
#include <limits.h>
|
|
|
+#include <mutex>
|
|
|
#include <pwd.h>
|
|
|
#include <signal.h>
|
|
|
#include <sstream>
|
|
|
@@ -18,6 +23,11 @@ namespace ngs {
|
|
|
namespace system {
|
|
|
namespace {
|
|
|
|
|
|
+constexpr auto kCacheTtl = std::chrono::milliseconds(800);
|
|
|
+constexpr size_t kMaxProcFileBytes = 4096;
|
|
|
+constexpr size_t kMaxCmdlineBytes = 512;
|
|
|
+constexpr size_t kMaxProcesses = 4096;
|
|
|
+
|
|
|
long clk_tck() {
|
|
|
static long v = 0;
|
|
|
if (v <= 0) {
|
|
|
@@ -40,6 +50,15 @@ int cpu_cores() {
|
|
|
return v;
|
|
|
}
|
|
|
|
|
|
+uint64_t page_size_bytes() {
|
|
|
+ static uint64_t v = 0;
|
|
|
+ if (v == 0) {
|
|
|
+ const long page = sysconf(_SC_PAGESIZE);
|
|
|
+ v = page > 0 ? static_cast<uint64_t>(page) : 4096ULL;
|
|
|
+ }
|
|
|
+ return v;
|
|
|
+}
|
|
|
+
|
|
|
uint64_t mem_total_bytes() {
|
|
|
struct sysinfo info {};
|
|
|
if (sysinfo(&info) != 0) {
|
|
|
@@ -58,34 +77,69 @@ double system_uptime_sec() {
|
|
|
return up > 0 ? up : 1.0;
|
|
|
}
|
|
|
|
|
|
-std::string read_file(const std::string& path) {
|
|
|
- std::ifstream in(path);
|
|
|
- if (!in) {
|
|
|
+std::string read_file_limited(const std::string& path, size_t max_bytes) {
|
|
|
+ const int fd = ::open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
|
|
+ if (fd < 0) {
|
|
|
return "";
|
|
|
}
|
|
|
- std::ostringstream ss;
|
|
|
- ss << in.rdbuf();
|
|
|
- return ss.str();
|
|
|
+ std::string out;
|
|
|
+ out.reserve(std::min(max_bytes, static_cast<size_t>(256)));
|
|
|
+ char buf[512];
|
|
|
+ while (out.size() < max_bytes) {
|
|
|
+ const size_t want = std::min(sizeof(buf), max_bytes - out.size());
|
|
|
+ const ssize_t n = ::read(fd, buf, want);
|
|
|
+ if (n <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ out.append(buf, static_cast<size_t>(n));
|
|
|
+ }
|
|
|
+ ::close(fd);
|
|
|
+ return out;
|
|
|
}
|
|
|
|
|
|
std::string uid_to_user(uid_t uid) {
|
|
|
+ static std::mutex mu;
|
|
|
static std::unordered_map<uid_t, std::string> cache;
|
|
|
+ {
|
|
|
+ std::lock_guard<std::mutex> lock(mu);
|
|
|
+ auto it = cache.find(uid);
|
|
|
+ if (it != cache.end()) {
|
|
|
+ return it->second;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ std::string name = std::to_string(uid);
|
|
|
+ long sz = sysconf(_SC_GETPW_R_SIZE_MAX);
|
|
|
+ if (sz < 256) {
|
|
|
+ sz = 4096;
|
|
|
+ }
|
|
|
+ std::vector<char> buf(static_cast<size_t>(sz));
|
|
|
+ passwd pwd {};
|
|
|
+ passwd* result = nullptr;
|
|
|
+ if (getpwuid_r(uid, &pwd, buf.data(), buf.size(), &result) == 0 &&
|
|
|
+ result && result->pw_name) {
|
|
|
+ name = result->pw_name;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::lock_guard<std::mutex> lock(mu);
|
|
|
auto it = cache.find(uid);
|
|
|
if (it != cache.end()) {
|
|
|
return it->second;
|
|
|
}
|
|
|
- passwd* pw = getpwuid(uid);
|
|
|
- std::string name = pw && pw->pw_name ? pw->pw_name : std::to_string(uid);
|
|
|
cache.emplace(uid, name);
|
|
|
return name;
|
|
|
}
|
|
|
|
|
|
std::string read_cmdline(int pid) {
|
|
|
const std::string path = "/proc/" + std::to_string(pid) + "/cmdline";
|
|
|
- std::string raw = read_file(path);
|
|
|
+ std::string raw = read_file_limited(path, kMaxCmdlineBytes + 1);
|
|
|
if (raw.empty()) {
|
|
|
return "";
|
|
|
}
|
|
|
+ const bool truncated = raw.size() > kMaxCmdlineBytes;
|
|
|
+ if (truncated) {
|
|
|
+ raw.resize(kMaxCmdlineBytes);
|
|
|
+ }
|
|
|
for (char& c : raw) {
|
|
|
if (c == '\0') {
|
|
|
c = ' ';
|
|
|
@@ -94,8 +148,7 @@ std::string read_cmdline(int pid) {
|
|
|
while (!raw.empty() && (raw.back() == ' ' || raw.back() == '\0')) {
|
|
|
raw.pop_back();
|
|
|
}
|
|
|
- if (raw.size() > 512) {
|
|
|
- raw.resize(512);
|
|
|
+ if (truncated) {
|
|
|
raw += "…";
|
|
|
}
|
|
|
return raw;
|
|
|
@@ -111,7 +164,7 @@ std::string read_cwd(int pid) {
|
|
|
return std::string(buf, static_cast<size_t>(n));
|
|
|
}
|
|
|
|
|
|
-bool parse_stat_full(const std::string& content, ProcessInfo& out) {
|
|
|
+bool parse_stat_full(const std::string& content, ProcessInfo& out, double uptime) {
|
|
|
auto lpar = content.find('(');
|
|
|
auto rpar = content.rfind(')');
|
|
|
if (lpar == std::string::npos || rpar == std::string::npos || rpar <= lpar) {
|
|
|
@@ -155,14 +208,11 @@ bool parse_stat_full(const std::string& content, ProcessInfo& out) {
|
|
|
out.cpu_ticks = utime + stime;
|
|
|
out.threads = num_threads > 0 ? static_cast<int>(num_threads) : 1;
|
|
|
out.vms_bytes = vsize;
|
|
|
- const long page = sysconf(_SC_PAGESIZE);
|
|
|
- const uint64_t page_sz = page > 0 ? static_cast<uint64_t>(page) : 4096ULL;
|
|
|
- out.rss_bytes = rss > 0 ? static_cast<uint64_t>(rss) * page_sz : 0;
|
|
|
+ out.rss_bytes = rss > 0 ? static_cast<uint64_t>(rss) * page_size_bytes() : 0;
|
|
|
|
|
|
- const double up = system_uptime_sec();
|
|
|
const double start_sec = static_cast<double>(starttime) /
|
|
|
static_cast<double>(clk_tck());
|
|
|
- const double age = up > start_sec ? (up - start_sec) : up;
|
|
|
+ const double age = uptime > start_sec ? (uptime - start_sec) : uptime;
|
|
|
const double cpu_sec =
|
|
|
static_cast<double>(out.cpu_ticks) / static_cast<double>(clk_tck());
|
|
|
if (age > 0.01) {
|
|
|
@@ -218,17 +268,30 @@ bool contains_ci(const std::string& hay, const std::string& needle) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
-} // namespace
|
|
|
+bool matches_query(const ProcessInfo& info, const std::string& q) {
|
|
|
+ if (q.empty()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ const std::string pid_s = std::to_string(info.pid);
|
|
|
+ return contains_ci(pid_s, q) || contains_ci(info.name, q) ||
|
|
|
+ contains_ci(info.cmdline, q) || contains_ci(info.user, q) ||
|
|
|
+ contains_ci(info.cwd, q);
|
|
|
+}
|
|
|
|
|
|
-std::vector<ProcessInfo> list_processes(const std::string& q) {
|
|
|
+std::vector<ProcessInfo> scan_proc() {
|
|
|
std::vector<ProcessInfo> out;
|
|
|
DIR* dir = opendir("/proc");
|
|
|
if (!dir) {
|
|
|
return out;
|
|
|
}
|
|
|
+ out.reserve(256);
|
|
|
const uint64_t mem_total = mem_total_bytes();
|
|
|
+ const double uptime = system_uptime_sec();
|
|
|
dirent* ent = nullptr;
|
|
|
while ((ent = readdir(dir)) != nullptr) {
|
|
|
+ if (out.size() >= kMaxProcesses) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
if (ent->d_type != DT_DIR && ent->d_type != DT_UNKNOWN) {
|
|
|
continue;
|
|
|
}
|
|
|
@@ -251,12 +314,12 @@ std::vector<ProcessInfo> list_processes(const std::string& q) {
|
|
|
continue;
|
|
|
}
|
|
|
const std::string stat_path = std::string("/proc/") + name + "/stat";
|
|
|
- const std::string stat = read_file(stat_path);
|
|
|
+ const std::string stat = read_file_limited(stat_path, kMaxProcFileBytes);
|
|
|
if (stat.empty()) {
|
|
|
continue;
|
|
|
}
|
|
|
ProcessInfo info;
|
|
|
- if (!parse_stat_full(stat, info)) {
|
|
|
+ if (!parse_stat_full(stat, info, uptime)) {
|
|
|
continue;
|
|
|
}
|
|
|
info.user = uid_to_user(read_uid(pid));
|
|
|
@@ -266,17 +329,8 @@ std::vector<ProcessInfo> list_processes(const std::string& q) {
|
|
|
}
|
|
|
info.cwd = read_cwd(pid);
|
|
|
if (mem_total > 0) {
|
|
|
- info.mem_percent =
|
|
|
- 100.0 * static_cast<double>(info.rss_bytes) /
|
|
|
- static_cast<double>(mem_total);
|
|
|
- }
|
|
|
- if (!q.empty()) {
|
|
|
- const std::string pid_s = std::to_string(info.pid);
|
|
|
- if (!contains_ci(pid_s, q) && !contains_ci(info.name, q) &&
|
|
|
- !contains_ci(info.cmdline, q) && !contains_ci(info.user, q) &&
|
|
|
- !contains_ci(info.cwd, q)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ info.mem_percent = 100.0 * static_cast<double>(info.rss_bytes) /
|
|
|
+ static_cast<double>(mem_total);
|
|
|
}
|
|
|
out.push_back(std::move(info));
|
|
|
}
|
|
|
@@ -292,6 +346,72 @@ std::vector<ProcessInfo> list_processes(const std::string& q) {
|
|
|
return out;
|
|
|
}
|
|
|
|
|
|
+struct SnapshotCache {
|
|
|
+ std::mutex mu;
|
|
|
+ std::condition_variable cv;
|
|
|
+ std::vector<ProcessInfo> items;
|
|
|
+ std::chrono::steady_clock::time_point at {};
|
|
|
+ bool valid = false;
|
|
|
+ bool scanning = false;
|
|
|
+};
|
|
|
+
|
|
|
+SnapshotCache& snapshot_cache() {
|
|
|
+ static SnapshotCache c;
|
|
|
+ return c;
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<ProcessInfo> snapshot_processes() {
|
|
|
+ auto& c = snapshot_cache();
|
|
|
+ std::unique_lock<std::mutex> lock(c.mu);
|
|
|
+ const auto now = std::chrono::steady_clock::now();
|
|
|
+ if (c.valid && now - c.at < kCacheTtl) {
|
|
|
+ return c.items;
|
|
|
+ }
|
|
|
+ if (c.scanning) {
|
|
|
+ c.cv.wait(lock, [&] { return !c.scanning; });
|
|
|
+ if (c.valid) {
|
|
|
+ return c.items;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c.scanning = true;
|
|
|
+ lock.unlock();
|
|
|
+
|
|
|
+ std::vector<ProcessInfo> items;
|
|
|
+ try {
|
|
|
+ items = scan_proc();
|
|
|
+ } catch (...) {
|
|
|
+ std::lock_guard<std::mutex> relock(c.mu);
|
|
|
+ c.scanning = false;
|
|
|
+ c.cv.notify_all();
|
|
|
+ throw;
|
|
|
+ }
|
|
|
+
|
|
|
+ lock.lock();
|
|
|
+ c.items = items;
|
|
|
+ c.at = std::chrono::steady_clock::now();
|
|
|
+ c.valid = true;
|
|
|
+ c.scanning = false;
|
|
|
+ c.cv.notify_all();
|
|
|
+ return items;
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+std::vector<ProcessInfo> list_processes(const std::string& q) {
|
|
|
+ auto all = snapshot_processes();
|
|
|
+ if (q.empty()) {
|
|
|
+ return all;
|
|
|
+ }
|
|
|
+ std::vector<ProcessInfo> out;
|
|
|
+ out.reserve(all.size());
|
|
|
+ for (auto& p : all) {
|
|
|
+ if (matches_query(p, q)) {
|
|
|
+ out.push_back(std::move(p));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
bool kill_process(int pid, int signal, std::string& err) {
|
|
|
err.clear();
|
|
|
if (pid <= 1) {
|