1use std::fs;
2use std::io::prelude::*;
3
4use lazy_static::lazy_static;
5use memoize::memoize;
6use rayon::prelude::*;
7use regex::Regex;
8
9use crate::DeviceResult;
10
11lazy_static! {
12 static ref DEVICE_PATH_PATTERN: Regex = Regex::new(
13 r"^/dev/npu(?P<device_id>\d+)((?:pe)(?P<start_core>\d+)(-(?P<end_core>\d+))?)?$"
14 )
15 .unwrap();
16}
17
18pub struct NpuProcess {
19 pub dev_name: String,
20 pub pid: u32,
21 pub cmdline: String,
22}
23
24impl NpuProcess {
25 pub(crate) fn new(dev_name: String, pid: u32, cmdline: String) -> Self {
26 Self {
27 dev_name,
28 pid,
29 cmdline,
30 }
31 }
32
33 pub fn dev_name(&self) -> &str {
34 self.dev_name.as_str()
35 }
36
37 pub fn pid(&self) -> u32 {
38 self.pid
39 }
40
41 pub fn cmdline(&self) -> &str {
42 self.cmdline.as_str()
43 }
44}
45
46pub fn scan_processes() -> DeviceResult<Vec<NpuProcess>> {
47 let mut targets = Vec::new();
48
49 for entry in fs::read_dir("/proc")? {
50 if let Ok(pid) = entry?.file_name().to_string_lossy().parse::<u32>() {
51 let path = format!("/proc/{pid}/fd");
52 if let Ok(dirs) = fs::read_dir(&path) {
53 for entry in dirs {
54 let entry = entry?;
55 targets.push((
56 pid,
57 format!(
58 "{}/{}",
59 path,
60 entry.file_name().as_os_str().to_string_lossy()
61 ),
62 ));
63 }
64 }
65 }
66 }
67
68 let mut results: Vec<NpuProcess> = targets
69 .into_par_iter()
70 .filter_map(|(pid, path)| {
71 if let Ok(link) = fs::read_link(path) {
72 let link = link.as_os_str().to_string_lossy();
73 if DEVICE_PATH_PATTERN.is_match(&link) {
74 return Some((pid, link.replace("/dev/", "")));
75 }
76 }
77 None
78 })
79 .filter_map(|(pid, dev_path)| {
80 read_cmdline(pid).map(|cmdline| NpuProcess::new(dev_path, pid, cmdline))
81 })
82 .collect();
83
84 results.sort_by(|a, b| a.dev_name.cmp(&b.dev_name));
85
86 Ok(results)
87}
88
89#[memoize(TimeToLive: std::time::Duration::from_secs(1))]
90fn read_cmdline(pid: u32) -> Option<String> {
91 let path = format!("/proc/{pid}/cmdline");
92 let file = std::fs::File::open(path).ok()?;
93 let mut buf_reader = std::io::BufReader::new(file);
94 let mut contents = String::new();
95 buf_reader.read_to_string(&mut contents).ok()?;
96
97 Some(contents.replace('\0', " ").trim().to_string())
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn read_cmdline_test() {
106 let my_pid = std::process::id();
107 let res = read_cmdline(my_pid);
108 assert!(res.is_some());
109
110 let res = res.unwrap();
111 assert!(res.contains("/target/"));
112 }
116}