Преглед на файлове

修复挂着进程列表会CPU和内存暴涨

你的用户名 преди 4 седмици
родител
ревизия
d22c87b68b
променени са 3 файла, в които са добавени 180 реда и са изтрити 41 реда
  1. 25 8
      data/www/assets/app.js
  2. 152 32
      src/system/process.cpp
  3. 3 1
      src/system/process.h

+ 25 - 8
data/www/assets/app.js

@@ -62,6 +62,8 @@
     procSort: "cpu_percent",
     procSortDir: "desc",
     procTimer: null,
+    procLoading: false,
+    procReloadQueued: false,
     shellTerm: null,
     shellFit: null,
     shellWs: null,
@@ -1277,6 +1279,8 @@
     state.procTimer = setInterval(() => {
       const panel = $("#view-processes");
       if (!panel || !panel.classList.contains("is-active")) return;
+      // Skip if a request is already in flight — do not queue extra scans.
+      if (state.procLoading) return;
       loadProcesses({ silent: true }).catch(() => {});
     }, 3000);
   }
@@ -1339,15 +1343,28 @@
   }
 
   async function loadProcesses(opts = {}) {
-    const q = String(state.procQuery || "").trim();
-    const qs = q ? `?q=${encodeURIComponent(q)}` : "";
-    const tbody = $("#proc-table tbody");
-    if (!opts.silent && tbody) {
-      tbody.innerHTML = `<tr><td colspan="10" class="text-secondary text-center py-4">加载中…</td></tr>`;
+    if (state.procLoading) {
+      if (!opts.silent) state.procReloadQueued = true;
+      return;
+    }
+    state.procLoading = true;
+    try {
+      const q = String(state.procQuery || "").trim();
+      const qs = q ? `?q=${encodeURIComponent(q)}` : "";
+      const tbody = $("#proc-table tbody");
+      if (!opts.silent && tbody) {
+        tbody.innerHTML = `<tr><td colspan="10" class="text-secondary text-center py-4">加载中…</td></tr>`;
+      }
+      const data = await api(`/api/system/processes${qs}`);
+      state.processes = (data && data.items) || [];
+      renderProcesses();
+    } finally {
+      state.procLoading = false;
+      if (state.procReloadQueued) {
+        state.procReloadQueued = false;
+        loadProcesses({ silent: true }).catch(() => {});
+      }
     }
-    const data = await api(`/api/system/processes${qs}`);
-    state.processes = (data && data.items) || [];
-    renderProcesses();
   }
 
   async function killProcess(pid, signalName) {

+ 152 - 32
src/system/process.cpp

@@ -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) {

+ 3 - 1
src/system/process.h

@@ -24,7 +24,9 @@ struct ProcessInfo {
     uint64_t cpu_ticks = 0;
 };
 
-// Enumerate /proc processes. Optional q filters name/cmdline/user/pid.
+// Enumerate /proc processes. Concurrent callers share one in-flight scan
+// (plus an 800ms snapshot) so auto-refresh cannot pile up full /proc walks.
+// Optional q filters name/cmdline/user/pid.
 std::vector<ProcessInfo> list_processes(const std::string& q = "");
 
 // Send signal to pid. signal: 15 (TERM) or 9 (KILL). Rejects pid <= 1.