pingap_performance/
process.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use bytesize::ByteSize;
16use memory_stats::memory_stats;
17use once_cell::sync::Lazy;
18use pingap_config::get_current_config;
19use serde::{Deserialize, Serialize};
20use std::process;
21use std::sync::atomic::{AtomicI32, AtomicU64, Ordering};
22use sysinfo::MemoryRefreshKind;
23use sysinfo::{RefreshKind, System};
24
25static ACCEPTED: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(0));
26static PROCESSING: Lazy<AtomicI32> = Lazy::new(|| AtomicI32::new(0));
27
28/// Increments the request acceptance and processing counters.
29/// This should be called when a new request is received to track request metrics.
30pub fn accept_request() {
31    ACCEPTED.fetch_add(1, Ordering::Relaxed);
32    PROCESSING.fetch_add(1, Ordering::Relaxed);
33}
34
35/// Decrements the request processing counter when a request completes.
36/// This should be called when a request finishes processing to maintain accurate metrics.
37pub fn end_request() {
38    PROCESSING.fetch_sub(1, Ordering::Relaxed);
39}
40
41/// Returns a tuple of (currently processing requests, total accepted requests).
42///
43/// Returns:
44/// - `i32`: Number of requests currently being processed
45/// - `u64`: Total number of requests accepted since startup
46pub fn get_processing_accepted() -> (i32, u64) {
47    let processing = PROCESSING.load(Ordering::Relaxed);
48    let accepted = ACCEPTED.load(Ordering::Relaxed);
49    (processing, accepted)
50}
51
52#[derive(Serialize, Deserialize, Debug)]
53pub struct ProcessSystemInfo {
54    /// Current memory usage in megabytes
55    pub memory_mb: usize,
56    /// Current memory usage as a human-readable string (e.g. "100 MB")
57    pub memory: String,
58    /// CPU architecture (e.g. "x86_64", "aarch64")
59    pub arch: String,
60    /// Number of logical CPU cores
61    pub cpus: usize,
62    /// Number of physical CPU cores
63    pub physical_cpus: usize,
64    /// Total system memory as a human-readable string
65    pub total_memory: String,
66    /// Used system memory as a human-readable string
67    pub used_memory: String,
68    /// Kernel version string
69    pub kernel: String,
70    /// Process ID of the current process
71    pub pid: u32,
72    /// Number of threads configured across all servers
73    pub threads: usize,
74    /// Number of open file descriptors (Linux only)
75    pub fd_count: usize,
76    /// Number of IPv4 TCP connections (Linux only)
77    pub tcp_count: usize,
78    /// Number of IPv6 TCP connections (Linux only)
79    pub tcp6_count: usize,
80}
81
82/// Gathers and returns system information including memory usage, CPU details,
83/// process statistics and network connection counts
84pub fn get_process_system_info() -> ProcessSystemInfo {
85    let current_config = get_current_config();
86    let data =
87        std::fs::read(current_config.basic.get_pid_file()).unwrap_or_default();
88    let mut pid = std::string::String::from_utf8_lossy(&data)
89        .trim()
90        .parse::<u32>()
91        .unwrap_or_default();
92    if pid == 0 {
93        pid = process::id();
94    }
95
96    cfg_if::cfg_if! {
97        if #[cfg(target_os = "linux")] {
98            let mut fd_count = 0;
99            let mut tcp_count = 0;
100            let mut tcp6_count =0;
101            if let Ok(p) = procfs::process::Process::new(pid as i32) {
102                fd_count = p.fd_count().unwrap_or_default();
103                tcp_count = p.tcp().unwrap_or_default().len();
104                tcp6_count = p.tcp6().unwrap_or_default().len();
105            }
106        } else {
107            let fd_count = 0;
108            let tcp_count = 0;
109            let tcp6_count =0;
110        }
111    }
112
113    let mut threads = 0;
114    let cpu_count = num_cpus::get();
115    let mut default_threads = current_config.basic.threads.unwrap_or(1);
116    if default_threads == 0 {
117        default_threads = cpu_count;
118    }
119    for (_, server) in current_config.servers.iter() {
120        let count = server.threads.unwrap_or(1);
121        if count == 0 {
122            threads += default_threads;
123        } else {
124            threads += count;
125        }
126    }
127
128    let mut memory = "".to_string();
129    let mut memory_mb = 0;
130    if let Some(value) = memory_stats() {
131        memory_mb = value.physical_mem / (1024 * 1024);
132        memory = ByteSize(value.physical_mem as u64).to_string();
133    }
134    let cpus = num_cpus::get();
135    let physical_cpus = num_cpus::get_physical();
136    let kind = MemoryRefreshKind::nothing().with_ram();
137    let mut sys =
138        System::new_with_specifics(RefreshKind::nothing().with_memory(kind));
139    sys.refresh_memory();
140
141    ProcessSystemInfo {
142        memory,
143        memory_mb,
144        arch: System::cpu_arch(),
145        cpus,
146        physical_cpus,
147        kernel: System::kernel_version().unwrap_or_default(),
148        total_memory: ByteSize(sys.total_memory()).to_string(),
149        used_memory: ByteSize(sys.used_memory()).to_string(),
150        pid,
151        threads,
152        fd_count,
153        tcp_count,
154        tcp6_count,
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use pretty_assertions::assert_eq;
162
163    #[test]
164    fn test_get_process_system_info() {
165        let info = get_process_system_info();
166        assert_eq!(true, info.memory_mb > 0);
167        assert_eq!(true, !info.memory.is_empty());
168        assert_eq!(true, !info.arch.is_empty());
169        assert_eq!(true, info.cpus > 0);
170        assert_eq!(true, info.physical_cpus > 0);
171        assert_eq!(true, !info.kernel.is_empty());
172        assert_eq!(true, info.pid != 0);
173    }
174
175    #[test]
176    fn test_get_processing_accepted() {
177        let (processing, accepted) = get_processing_accepted();
178        assert_eq!(processing, 0);
179        assert_eq!(accepted, 0);
180        accept_request();
181        let (processing, accepted) = get_processing_accepted();
182        assert_eq!(processing, 1);
183        assert_eq!(accepted, 1);
184        end_request();
185        let (processing, accepted) = get_processing_accepted();
186        assert_eq!(processing, 0);
187        assert_eq!(accepted, 1);
188    }
189}