use super::EngineCounter;
use std::path::Path;
pub fn normalize_engine_class(raw: &str) -> &'static str {
let lowered = raw.to_ascii_lowercase();
match lowered.as_str() {
"rcs" | "render" => "render",
"ccs" | "compute" => "compute",
"bcs" | "copy" => "copy",
"vcs" | "video" | "video_decode" | "video-decode" => "video",
"vecs" | "video_enhance" | "video-enhance" => "video-enhance",
_ => "other",
}
}
pub fn discover_engine_counters(device_dir: &Path) -> Vec<EngineCounter> {
let mut out = Vec::new();
if let Some(card_dir) = device_dir.parent() {
collect_i915_counters(&card_dir.join("engine"), &mut out);
}
collect_i915_counters(&device_dir.join("engine"), &mut out);
for tile in 0u32..2 {
for gt in 0u32..2 {
let engines_dir = device_dir
.join(format!("tile{tile}"))
.join(format!("gt{gt}"))
.join("engines");
collect_xe_counters(&engines_dir, &mut out);
}
}
out.sort_by(|a, b| {
a.class
.cmp(b.class)
.then_with(|| a.instance.cmp(&b.instance))
});
out
}
fn collect_i915_counters(engine_root: &Path, out: &mut Vec<EngineCounter>) {
let entries = match std::fs::read_dir(engine_root) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()).map(String::from) else {
continue;
};
let busy = path.join("busy");
if busy.is_file() {
let (class_raw, instance) = split_class_instance(&name);
out.push(EngineCounter {
class: normalize_engine_class(class_raw),
instance,
path: busy,
});
continue;
}
if let Ok(inner) = std::fs::read_dir(&path) {
for inst_entry in inner.flatten() {
let inst_path = inst_entry.path();
let Some(inst_name) = inst_path
.file_name()
.and_then(|n| n.to_str())
.map(String::from)
else {
continue;
};
let inst_busy = inst_path.join("busy");
if inst_busy.is_file() {
out.push(EngineCounter {
class: normalize_engine_class(&name),
instance: inst_name,
path: inst_busy,
});
}
}
}
}
}
fn collect_xe_counters(engines_root: &Path, out: &mut Vec<EngineCounter>) {
let entries = match std::fs::read_dir(engines_root) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()).map(String::from) else {
continue;
};
let mut matched_flat = false;
for filename in ["busy_ns", "busy"] {
let candidate = path.join(filename);
if candidate.is_file() {
let (class_raw, instance) = split_class_instance(&name);
out.push(EngineCounter {
class: normalize_engine_class(class_raw),
instance,
path: candidate,
});
matched_flat = true;
break;
}
}
if matched_flat {
continue;
}
if let Ok(inner) = std::fs::read_dir(&path) {
for inst_entry in inner.flatten() {
let inst_path = inst_entry.path();
let Some(inst_name) = inst_path
.file_name()
.and_then(|n| n.to_str())
.map(String::from)
else {
continue;
};
for filename in ["busy_ns", "busy"] {
let inst_busy = inst_path.join(filename);
if inst_busy.is_file() {
out.push(EngineCounter {
class: normalize_engine_class(&name),
instance: inst_name.clone(),
path: inst_busy,
});
break;
}
}
}
}
}
}
pub(super) fn split_class_instance(name: &str) -> (&str, String) {
let mut split_idx: Option<usize> = None;
for (i, c) in name.char_indices().rev() {
if c.is_ascii_digit() {
split_idx = Some(i);
} else {
break;
}
}
match split_idx {
Some(i) if i > 0 => (&name[..i], name[i..].to_string()),
Some(_) => (name, String::new()),
None => (name, String::new()),
}
}