use std::collections::{BTreeMap, BTreeSet};
use std::num::NonZeroU64;
use kernel::{AxError, RunId};
use super::{Backlog, BacklogId, Body, Claim, Member};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RunProcesses {
pub pids: BTreeSet<u32>,
pub unfollowed: u32,
pub share: Shares,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Shares {
#[default]
Unset,
Cpu,
CpuAndMemory {
limit: NonZeroU64,
},
}
#[derive(Default)]
pub(super) struct Jobs {
#[cfg(windows)]
runs: BTreeMap<RunId, RunJob>,
#[cfg(not(windows))]
cgroups: super::cgroup::Cgroups,
}
impl Backlog {
pub fn processes(&self) -> Result<BTreeMap<RunId, RunProcesses>, AxError> {
let table = self.hold()?;
let mut runs: BTreeMap<RunId, RunProcesses> = BTreeMap::new();
for member in table.members.values() {
if let Body::Command {
child,
claim: Claim::Window(owner) | Claim::Run(owner),
..
} = &member.body
{
runs.entry(*owner).or_default().pids.insert(child.id());
}
}
table.jobs.follow(&mut runs);
Ok(runs)
}
pub(super) fn enrol(&self, id: BacklogId, member: Member) -> Result<(), AxError> {
let mut table = self.hold()?;
table.jobs.enter(&member, self.shares);
table.members.insert(id, member);
Ok(())
}
}
impl Jobs {
#[cfg(windows)]
pub(super) fn forget(&mut self, owner: RunId) {
self.runs.remove(&owner);
}
#[cfg(not(windows))]
pub(super) fn forget(&mut self, owner: RunId) {
self.cgroups.forget(owner);
}
#[cfg(windows)]
fn enter(&mut self, member: &Member, shares: Shares) {
let Body::Command {
child,
claim: Claim::Window(owner) | Claim::Run(owner),
..
} = &member.body
else {
return;
};
let run = self.runs.entry(*owner).or_default();
if run.join(child, shares).is_none() {
run.unjoined = run.unjoined.saturating_add(1);
}
}
#[cfg(not(windows))]
fn enter(&mut self, member: &Member, shares: Shares) {
let Body::Command {
child,
claim: Claim::Window(owner) | Claim::Run(owner),
..
} = &member.body
else {
return;
};
self.cgroups.enter(*owner, child.id(), shares);
}
#[cfg(windows)]
fn follow(&self, readings: &mut BTreeMap<RunId, RunProcesses>) {
for (owner, run) in &self.runs {
let reading = readings.entry(*owner).or_default();
let listed = run.job.as_ref().map(win32job::Job::query_process_id_list);
if let Some(Ok(pids)) = listed {
reading
.pids
.extend(pids.into_iter().filter_map(|pid| u32::try_from(pid).ok()));
reading.unfollowed = reading.unfollowed.saturating_add(run.unjoined);
reading.share = run.share;
} else {
reading.unfollowed = u32::try_from(reading.pids.len()).unwrap_or(u32::MAX);
}
}
}
#[cfg(not(windows))]
fn follow(&self, readings: &mut BTreeMap<RunId, RunProcesses>) {
for (owner, reading) in readings.iter_mut() {
reading.unfollowed = u32::try_from(reading.pids.len()).unwrap_or(u32::MAX);
reading.share = self.cgroups.held(*owner);
}
}
}
#[cfg(windows)]
const RUN_CPU_WEIGHT: u32 = 5;
#[cfg(windows)]
#[derive(Default)]
struct RunJob {
job: Option<win32job::Job>,
unjoined: u32,
share: Shares,
}
#[cfg(windows)]
impl RunJob {
fn join(&mut self, child: &std::process::Child, shares: Shares) -> Option<()> {
use std::os::windows::io::AsRawHandle;
let handle = isize::try_from(child.as_raw_handle().addr()).ok()?;
let job = match self.job.take() {
Some(job) => job,
None => {
let job = win32job::Job::create().ok()?;
self.share = given(&job, shares);
job
}
};
let joined = job.assign_process(handle).ok();
self.job = Some(job);
joined
}
}
#[cfg(windows)]
fn given(job: &win32job::Job, asked: Shares) -> Shares {
let memory = match asked {
Shares::Unset => return Shares::Unset,
Shares::Cpu => 0,
Shares::CpuAndMemory { limit } => match usize::try_from(limit.get()) {
Ok(bytes) => bytes,
Err(_beyond) => return weigh(job, 0, Shares::Cpu),
},
};
weigh(job, memory, asked)
}
#[cfg(windows)]
fn weigh(job: &win32job::Job, memory: usize, held: Shares) -> Shares {
match desktop_ffi::cpu::job_share(job.handle(), RUN_CPU_WEIGHT, memory) {
Ok(()) => held,
Err(_refused) => Shares::Unset,
}
}