diff --git a/lib/psutil.cc b/lib/psutil.cc index 0528ca8e38455d07afd3d5755ecd729b2a13c5b6..928d66e42720345e65c5d31b8ad4f1aaa51a4a81 100644 --- a/lib/psutil.cc +++ b/lib/psutil.cc @@ -2,21 +2,34 @@ #include #include +#include #ifdef __APPLE__ +#include #include #include #include #else -#include -#include #include +#include #include -#include #include +#include #include #endif +struct CPUTime { +#ifdef __APPLE__ + uint64_t user; + uint64_t system; +#else + unsigned long long utime; + unsigned long long stime; + unsigned long long cutime; + unsigned long long cstime; +#endif +}; + int get_num_cores() { #ifdef __APPLE__ int nm[2]; @@ -38,23 +51,24 @@ int get_num_cores() { #endif } -double get_cpu_time(int64_t pid) { +CPUTime get_cpu_time(int64_t pid) { #ifdef __APPLE__ struct proc_taskinfo pti; int ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)); - + if (ret <= 0) { - return 0.0; + return {0, 0}; } - - return (pti.pti_total_user + pti.pti_total_system) / 1e9; + + return {pti.pti_total_user, pti.pti_total_system}; #else std::string stat_path = "/proc/" + std::to_string(pid) + "/stat"; std::ifstream stat_file(stat_path); + CPUTime result = {0, 0, 0, 0}; if (!stat_file.is_open()) { std::cerr << "Failed to open " << stat_path << std::endl; - return -1.0; + return result; } std::string line; @@ -68,42 +82,62 @@ double get_cpu_time(int64_t pid) { tokens.push_back(token); } - if (tokens.size() < 15) { + if (tokens.size() < 17) { std::cerr << "Unexpected format in " << stat_path << std::endl; - return -1.0; + return result; } - unsigned long long utime = std::stoull(tokens[13]); - unsigned long long stime = std::stoull(tokens[14]); - double ticks_per_second = static_cast(sysconf(_SC_CLK_TCK)); + result.utime = std::stoull(tokens[13]); + result.stime = std::stoull(tokens[14]); + result.cutime = std::stoull(tokens[15]); + result.cstime = std::stoull(tokens[16]); - return (utime + stime) / ticks_per_second; + return result; #endif } double get_process_cpu_usage_percentage(int64_t pid) { - const std::chrono::milliseconds measurement_interval(100); - double cpu_time_start = get_cpu_time(pid); + static std::unordered_map last_cpu_times; + static std::unordered_map last_cpu_percentages; + const std::chrono::milliseconds measurement_interval(200); + static int num_cores = get_num_cores(); - if (cpu_time_start < 0) { - return 0.0; - } + CPUTime start_time = get_cpu_time(pid); + auto start = std::chrono::steady_clock::now(); - auto start_time = std::chrono::steady_clock::now(); std::this_thread::sleep_for(measurement_interval); - auto end_time = std::chrono::steady_clock::now(); - double cpu_time_end = get_cpu_time(pid); - if (cpu_time_end < 0) { + CPUTime end_time = get_cpu_time(pid); + auto end = std::chrono::steady_clock::now(); + + double elapsed_seconds = std::chrono::duration(end - start).count(); + + if (last_cpu_times.find(pid) == last_cpu_times.end()) { + last_cpu_times[pid] = start_time; + last_cpu_percentages[pid] = 0.0; return 0.0; } - long num_cores = get_num_cores(); - double cpu_time_diff = cpu_time_end - cpu_time_start; - std::chrono::duration elapsed = end_time - start_time; + CPUTime& last_time = last_cpu_times[pid]; - double elapsed_seconds = elapsed.count(); - double cpu_usage_percentage = (cpu_time_diff / elapsed_seconds) * (100.0 * num_cores); +#ifdef __APPLE__ + uint64_t user_ticks = end_time.user - last_time.user; + uint64_t system_ticks = end_time.system - last_time.system; + uint64_t total_ticks = user_ticks + system_ticks; + double seconds = static_cast(total_ticks) / 1e9; +#else + unsigned long long total_time = + (end_time.utime + end_time.stime + end_time.cutime + end_time.cstime) - + (last_time.utime + last_time.stime + last_time.cutime + last_time.cstime); + + double seconds = static_cast(total_time) / sysconf(_SC_CLK_TCK); +#endif + double cpu_usage = 100.0 * (seconds / elapsed_seconds) / num_cores; + last_cpu_times[pid] = end_time; - return std::min(cpu_usage_percentage, 100.0 * num_cores); -} \ No newline at end of file + double& last_percentage = last_cpu_percentages[pid]; + cpu_usage = (cpu_usage * 0.3) + (last_percentage * 0.2); + last_percentage = cpu_usage; + + return std::min(cpu_usage, 100.0 * num_cores); +} diff --git a/src/daemon/api/routes.rs b/src/daemon/api/routes.rs index e3258c3df6caf936c97cb9f06499c581245be5d4..556a8868be91f391f156c1d32a5394411d8edef8 100644 --- a/src/daemon/api/routes.rs +++ b/src/daemon/api/routes.rs @@ -903,30 +903,39 @@ pub async fn remote_metrics(name: String, _t: Token) -> Result #[get("/live/daemon//metrics")] pub async fn stream_metrics(server: String, _t: Token) -> EventStream![] { EventStream! { - - if let Some(servers) = config::servers().servers { - let (address, (client, headers)) = match servers.get(&server) { - Some(server) => (&server.address, client(&server.token).await), - None => loop { - let response = get_metrics().await; - yield Event::data(serde_json::to_string(&response).unwrap()); - sleep(Duration::from_millis(500)) - }, - }; - - loop { - match client.get(fmtstr!("{address}/daemon/metrics")).headers(headers.clone()).send().await { - Ok(data) => { - if data.status() != 200 { - break yield Event::data(data.text().await.unwrap()); - } else { - yield Event::data(data.text().await.unwrap()); - sleep(Duration::from_millis(1500)); + match config::servers().servers { + Some(servers) => { + let (address, (client, headers)) = match servers.get(&server) { + Some(server) => (&server.address, client(&server.token).await), + None => match &*server { + "local" | "internal" => loop { + let response = get_metrics().await; + yield Event::data(serde_json::to_string(&response).unwrap()); + sleep(Duration::from_millis(500)); + }, + _ => return yield Event::data(format!("{{\"error\": \"server does not exist\"}}")), + } + }; + + loop { + match client.get(fmtstr!("{address}/daemon/metrics")).headers(headers.clone()).send().await { + Ok(data) => { + if data.status() != 200 { + break yield Event::data(data.text().await.unwrap()); + } else { + yield Event::data(data.text().await.unwrap()); + sleep(Duration::from_millis(1500)); + } } + Err(err) => break yield Event::data(format!("{{\"error\": \"{err}\"}}")), } - Err(err) => break yield Event::data(format!("{{\"error\": \"{err}\"}}")), } } + None => loop { + let response = get_metrics().await; + yield Event::data(serde_json::to_string(&response).unwrap()); + sleep(Duration::from_millis(500)) + }, }; } } @@ -936,29 +945,39 @@ pub async fn stream_info(server: String, id: usize, _t: Token) -> EventStream![] EventStream! { let runner = Runner::new(); - if let Some(servers) = config::servers().servers { - let (address, (client, headers)) = match servers.get(&server) { - Some(server) => (&server.address, client(&server.token).await), - None => loop { - let item = runner.refresh().get(id); - yield Event::data(serde_json::to_string(&item.fetch()).unwrap()); - sleep(Duration::from_millis(1000)) - }, - }; - - loop { - match client.get(fmtstr!("{address}/process/{id}/info")).headers(headers.clone()).send().await { - Ok(data) => { - if data.status() != 200 { - break yield Event::data(data.text().await.unwrap()); - } else { - yield Event::data(data.text().await.unwrap()); - sleep(Duration::from_millis(1500)); + match config::servers().servers { + Some(servers) => { + let (address, (client, headers)) = match servers.get(&server) { + Some(server) => (&server.address, client(&server.token).await), + None => match &*server { + "local" | "internal" => loop { + let item = runner.refresh().get(id); + yield Event::data(serde_json::to_string(&item.fetch()).unwrap()); + sleep(Duration::from_millis(1000)); + }, + _ => return yield Event::data(format!("{{\"error\": \"server does not exist\"}}")), + } + }; + + loop { + match client.get(fmtstr!("{address}/process/{id}/info")).headers(headers.clone()).send().await { + Ok(data) => { + if data.status() != 200 { + break yield Event::data(data.text().await.unwrap()); + } else { + yield Event::data(data.text().await.unwrap()); + sleep(Duration::from_millis(1500)); + } } + Err(err) => break yield Event::data(format!("{{\"error\": \"{err}\"}}")), } - Err(err) => break yield Event::data(format!("{{\"error\": \"{err}\"}}")), } } + None => loop { + let item = runner.refresh().get(id); + yield Event::data(serde_json::to_string(&item.fetch()).unwrap()); + sleep(Duration::from_millis(1000)); + } }; } }