#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
use std::ffi::{CStr, CString};
use libc::{c_char, c_int};
use crate::ported::linux::compat::{openat_arg_t, Compat_openat, Compat_readfileat};
use crate::ported::linux::linuxmachine::{GPUEngineData, LinuxMachine};
use crate::ported::linux::linuxprocess::LinuxProcess;
use crate::ported::linux::linuxprocesstable::LinuxProcessTable;
use crate::ported::machine::Machine;
use crate::ported::xutils::{String_eq_nullable, String_startsWith};
type ClientID = u64;
const INVALID_CLIENT_ID: ClientID = ClientID::MAX;
struct ClientInfo {
pdev: Option<String>,
id: ClientID,
next: Option<Box<ClientInfo>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum section_state {
SECST_UNKNOWN,
SECST_DUPLICATE,
SECST_NEW,
}
fn is_duplicate_client(mut parsed: Option<&ClientInfo>, id: ClientID, pdev: Option<&str>) -> bool {
while let Some(node) = parsed {
if id == node.id && String_eq_nullable(pdev, node.pdev.as_deref()) {
return true;
}
parsed = node.next.as_deref();
}
false
}
fn update_machine_gpu(lpt: &mut LinuxProcessTable, time: u64, engine: &str) {
let host = lpt.super_.super_.host;
debug_assert!(!host.is_null());
let lhost: &mut LinuxMachine = unsafe { &mut *(host as *mut Machine as *mut LinuxMachine) };
{
let mut engineData = &mut lhost.gpuEngineData;
loop {
match engineData {
None => break,
Some(node) if node.key.as_deref() == Some(engine) => break,
Some(_) => {}
}
engineData = &mut engineData.as_mut().unwrap().next;
}
if engineData.is_none() {
*engineData = Some(Box::new(GPUEngineData {
prevTime: 0,
curTime: 0,
key: Some(engine.to_string()),
next: None,
}));
}
engineData.as_mut().unwrap().curTime += time;
}
lhost.curGpuTime += time;
}
pub fn GPU_readProcessData(
lpt: &mut LinuxProcessTable,
lp: &mut LinuxProcess,
procFd: openat_arg_t,
) {
let host = lp.super_.super_.host as *const Machine;
let mut parsed_ids: Option<Box<ClientInfo>> = None;
let mut new_gpu_time: u64 = 0;
let host_monotonicMs = unsafe { (*host).monotonicMs };
let host_prevMonotonicMs = unsafe { (*host).prevMonotonicMs };
if lp.gpu_activityMs != 0 && host_monotonicMs - lp.gpu_activityMs < 5000 {
lp.gpu_percent = 0.0;
return;
}
lp.gpu_activityMs = host_monotonicMs;
let errno_location = || -> *mut c_int {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
unsafe { libc::__errno_location() }
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
{
unsafe { libc::__errno() }
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
{
unsafe { libc::___errno() }
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris",
target_os = "illumos"
)))]
{
unsafe { libc::__error() }
}
};
let strtoull_c = |s: &str| -> (u64, usize, bool) {
let cs = CString::new(s).unwrap_or_default();
unsafe {
*errno_location() = 0;
let mut endptr: *mut c_char = std::ptr::null_mut();
let val = libc::strtoull(cs.as_ptr(), &mut endptr, 10);
let err = *errno_location();
let consumed = endptr.offset_from(cs.as_ptr()) as usize;
(val, consumed, err != 0)
}
};
let mut fdinfoFd: c_int;
let mut fdinfoDir: *mut libc::DIR = std::ptr::null_mut();
'out: {
fdinfoFd = Compat_openat(
procFd,
c"fdinfo",
libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY | libc::O_CLOEXEC,
);
if fdinfoFd == -1 {
break 'out;
}
fdinfoDir = unsafe { libc::fdopendir(fdinfoFd) };
if fdinfoDir.is_null() {
break 'out;
}
fdinfoFd = -1;
loop {
let mut pdev: Option<String> = None;
let mut client_id: ClientID = INVALID_CLIENT_ID;
let mut sstate = section_state::SECST_UNKNOWN;
let entry = unsafe { libc::readdir(fdinfoDir) };
if entry.is_null() {
break;
}
let ename = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()) };
if ename.to_bytes() == b"." || ename.to_bytes() == b".." {
continue;
}
let mut buffer = [0u8; 4096];
let ret = Compat_readfileat(unsafe { libc::dirfd(fdinfoDir) }, ename, &mut buffer);
if ret <= 0 || (ret as usize) >= buffer.len() - 1 {
continue;
}
let content = &buffer[..ret as usize];
for line_bytes in content.split(|&b| b == b'\n') {
let line_cow = String::from_utf8_lossy(line_bytes);
let line: &str = &line_cow;
if !String_startsWith(line, "drm-") {
continue;
}
let line = &line["drm-".len()..];
if line.starts_with('c') && String_startsWith(line, "client-id:") {
if sstate == section_state::SECST_NEW {
debug_assert!(client_id != INVALID_CLIENT_ID);
parsed_ids = Some(Box::new(ClientInfo {
id: client_id,
pdev: pdev.take(),
next: parsed_ids.take(),
}));
}
sstate = section_state::SECST_UNKNOWN;
let rest = &line["client-id:".len()..];
let (val, consumed, err) = strtoull_c(rest);
client_id = if err || consumed != rest.len() {
INVALID_CLIENT_ID
} else {
val
};
} else if line.starts_with('p') && String_startsWith(line, "pdev:") {
let p = &line["pdev:".len()..];
let pb = p.as_bytes();
let mut i = 0;
while i < pb.len()
&& matches!(pb[i], b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
{
i += 1;
}
let p = &p[i..];
debug_assert!(pdev.is_none() || pdev.as_deref() == Some(p));
if pdev.is_none() {
pdev = Some(p.to_string());
}
} else if line.starts_with('e') && String_startsWith(line, "engine-") {
if sstate == section_state::SECST_DUPLICATE {
continue;
}
let engineStart = &line["engine-".len()..];
if String_startsWith(engineStart, "capacity-") {
continue;
}
let delim = match line.find(':') {
Some(d) => d,
None => continue,
};
let after = &line[delim + 1..];
let (value, consumed, err) = strtoull_c(after);
if !err && String_startsWith(&after[consumed..], " ns") {
if sstate == section_state::SECST_UNKNOWN {
if client_id != INVALID_CLIENT_ID
&& !is_duplicate_client(
parsed_ids.as_deref(),
client_id,
pdev.as_deref(),
)
{
sstate = section_state::SECST_NEW;
} else {
sstate = section_state::SECST_DUPLICATE;
}
}
if sstate == section_state::SECST_NEW {
new_gpu_time += value;
let engine = &engineStart[..delim - "engine-".len()];
update_machine_gpu(lpt, value, engine);
}
}
}
}
if sstate == section_state::SECST_NEW {
debug_assert!(client_id != INVALID_CLIENT_ID);
parsed_ids = Some(Box::new(ClientInfo {
id: client_id,
pdev: pdev.take(),
next: parsed_ids.take(),
}));
}
}
if new_gpu_time > 0 {
let gputimeDelta = new_gpu_time.saturating_sub(lp.gpu_time);
let monotonicTimeDelta = host_monotonicMs - host_prevMonotonicMs;
lp.gpu_percent =
100.0f32 * gputimeDelta as f32 / (1000.0 * 1000.0) / monotonicTimeDelta as f32;
lp.gpu_activityMs = 0;
} else {
lp.gpu_percent = 0.0;
}
}
lp.gpu_time = new_gpu_time;
drop(parsed_ids);
if !fdinfoDir.is_null() {
unsafe {
libc::closedir(fdinfoDir);
}
}
if fdinfoFd != -1 {
unsafe {
libc::close(fdinfoFd);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_duplicate_client_matches_id_and_pdev() {
let list = ClientInfo {
id: 7,
pdev: Some("0000:01:00.0".to_string()),
next: Some(Box::new(ClientInfo {
id: 3,
pdev: None,
next: None,
})),
};
assert!(is_duplicate_client(Some(&list), 7, Some("0000:01:00.0")));
assert!(!is_duplicate_client(Some(&list), 7, None));
assert!(is_duplicate_client(Some(&list), 3, None));
assert!(!is_duplicate_client(Some(&list), 9, None));
assert!(!is_duplicate_client(None, 7, Some("0000:01:00.0")));
}
}