#![allow(dead_code)]
extern crate alloc;
use alloc::vec::Vec;
use crate::io;
use crate::sys;
use super::get_arg;
const SCHED_ATTR_SIZE: u32 = 56;
const SCHED_NORMAL: u32 = 0;
const SCHED_FIFO: u32 = 1;
const SCHED_RR: u32 = 2;
const SCHED_BATCH: u32 = 3;
const SCHED_IDLE: u32 = 5;
const SCHED_DEADLINE: u32 = 6;
const SCHED_FLAG_RESET_ON_FORK: u64 = 0x01;
const SCHED_FLAG_RECLAIM: u64 = 0x02;
const SCHED_FLAG_DL_OVERRUN: u64 = 0x04;
const SCHED_FLAG_KEEP_POLICY: u64 = 0x08;
const SCHED_FLAG_KEEP_PARAMS: u64 = 0x10;
const SCHED_FLAG_UTIL_CLAMP_MIN: u64 = 0x20;
const SCHED_FLAG_UTIL_CLAMP_MAX: u64 = 0x40;
const SCHED_FLAG_UTIL_CLAMP: u64 = SCHED_FLAG_UTIL_CLAMP_MIN | SCHED_FLAG_UTIL_CLAMP_MAX;
#[repr(C)]
#[derive(Clone, Copy)]
struct SchedAttr {
size: u32,
sched_policy: u32,
sched_flags: u64,
sched_nice: i32,
sched_priority: u32,
sched_runtime: u64,
sched_deadline: u64,
sched_period: u64,
sched_util_min: u32,
sched_util_max: u32,
}
impl Default for SchedAttr {
fn default() -> Self {
Self {
size: SCHED_ATTR_SIZE,
sched_policy: 0,
sched_flags: 0,
sched_nice: 0,
sched_priority: 0,
sched_runtime: 0,
sched_deadline: 0,
sched_period: 0,
sched_util_min: 0,
sched_util_max: 1024,
}
}
}
#[cfg(target_os = "linux")]
pub fn uclampset(argc: i32, argv: *const *const u8) -> i32 {
let mut min: Option<u32> = None;
let mut max: Option<u32> = None;
let mut reset_on_fork = false;
let mut pid: Option<i32> = None;
let mut reset = false;
let mut show = false;
let mut command_start = 0usize;
let mut i = 1;
while i < argc as usize {
let arg = match unsafe { get_arg(argv, i as i32) } {
Some(a) => a,
None => break,
};
if arg == b"-m" {
i += 1;
if let Some(v) = unsafe { get_arg(argv, i as i32) } {
min = Some(sys::parse_u64(v).unwrap_or(0) as u32);
}
} else if arg == b"-M" {
i += 1;
if let Some(v) = unsafe { get_arg(argv, i as i32) } {
max = Some(sys::parse_u64(v).unwrap_or(1024) as u32);
}
} else if arg == b"-a" {
reset_on_fork = true;
} else if arg == b"-p" {
i += 1;
if let Some(p) = unsafe { get_arg(argv, i as i32) } {
pid = Some(sys::parse_i64(p).unwrap_or(0) as i32);
}
} else if arg == b"-R" {
reset = true;
} else if arg == b"-s" {
show = true;
} else if arg == b"-h" || arg == b"--help" {
print_usage();
return 0;
} else if !arg.starts_with(b"-") {
command_start = i;
break;
}
i += 1;
}
let target_pid = match pid {
Some(p) => p,
None => {
if command_start == 0 && !show {
io::write_str(2, b"uclampset: must specify pid or command\n");
print_usage();
return 1;
}
0 }
};
if show {
let p = if target_pid == 0 {
unsafe { libc::getpid() }
} else {
target_pid
};
return show_uclamp(p);
}
if pid.is_some() {
return set_uclamp(target_pid, min, max, reset, reset_on_fork);
}
if command_start > 0 {
if set_uclamp(0, min, max, reset, reset_on_fork) != 0 {
return 1;
}
let mut cmd_args: Vec<*const u8> = Vec::new();
for j in command_start..(argc as usize) {
if let Some(arg) = unsafe { get_arg(argv, j as i32) } {
let mut arg_cstr = Vec::from(arg);
arg_cstr.push(0);
cmd_args.push(arg_cstr.leak().as_ptr());
}
}
cmd_args.push(core::ptr::null());
if cmd_args.len() <= 1 {
io::write_str(2, b"uclampset: missing command\n");
return 1;
}
unsafe {
libc::execvp(cmd_args[0] as *const i8, cmd_args.as_ptr() as *const *const i8);
}
sys::perror(b"exec");
return 1;
}
io::write_str(2, b"uclampset: must specify pid or command\n");
1
}
#[cfg(target_os = "linux")]
fn set_uclamp(pid: i32, min: Option<u32>, max: Option<u32>, reset: bool, reset_on_fork: bool) -> i32 {
let mut attr = SchedAttr::default();
if unsafe { sched_getattr(pid, &mut attr, SCHED_ATTR_SIZE, 0) } < 0 {
sys::perror(b"sched_getattr");
return 1;
}
if reset {
attr.sched_util_min = 0;
attr.sched_util_max = 1024;
attr.sched_flags &= !(SCHED_FLAG_UTIL_CLAMP);
} else {
if let Some(m) = min {
attr.sched_util_min = m.min(1024);
attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
}
if let Some(m) = max {
attr.sched_util_max = m.min(1024);
attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
}
}
if reset_on_fork {
attr.sched_flags |= SCHED_FLAG_RESET_ON_FORK;
}
attr.sched_flags |= SCHED_FLAG_KEEP_POLICY | SCHED_FLAG_KEEP_PARAMS;
if unsafe { sched_setattr(pid, &attr, 0) } < 0 {
sys::perror(b"sched_setattr");
return 1;
}
0
}
#[cfg(target_os = "linux")]
fn show_uclamp(pid: i32) -> i32 {
let mut attr = SchedAttr::default();
if unsafe { sched_getattr(pid, &mut attr, SCHED_ATTR_SIZE, 0) } < 0 {
sys::perror(b"sched_getattr");
return 1;
}
io::write_str(1, b"pid ");
let mut num_buf = [0u8; 16];
io::write_all(1, sys::format_i64(pid as i64, &mut num_buf));
io::write_str(1, b"'s current uclamp values:\n");
io::write_str(1, b" util_min: ");
io::write_all(1, sys::format_u64(attr.sched_util_min as u64, &mut num_buf));
io::write_str(1, b"\n");
io::write_str(1, b" util_max: ");
io::write_all(1, sys::format_u64(attr.sched_util_max as u64, &mut num_buf));
io::write_str(1, b"\n");
0
}
#[cfg(target_os = "linux")]
unsafe fn sched_getattr(pid: i32, attr: *mut SchedAttr, size: u32, flags: u32) -> i32 { unsafe {
libc::syscall(libc::SYS_sched_getattr, pid, attr, size, flags) as i32
}}
#[cfg(target_os = "linux")]
unsafe fn sched_setattr(pid: i32, attr: *const SchedAttr, flags: u32) -> i32 { unsafe {
libc::syscall(libc::SYS_sched_setattr, pid, attr, flags) as i32
}}
fn print_usage() {
io::write_str(1, b"Usage: uclampset [options] command [args]\n");
io::write_str(1, b" uclampset [options] -p pid\n\n");
io::write_str(1, b"Set or show utilization clamping attributes.\n\n");
io::write_str(1, b"Options:\n");
io::write_str(1, b" -m MIN Set minimum utilization (0-1024)\n");
io::write_str(1, b" -M MAX Set maximum utilization (0-1024)\n");
io::write_str(1, b" -a Reset on fork\n");
io::write_str(1, b" -p PID Operate on existing process\n");
io::write_str(1, b" -R Reset to system defaults\n");
io::write_str(1, b" -s Show current settings\n");
}
#[cfg(not(target_os = "linux"))]
pub fn uclampset(_argc: i32, _argv: *const *const u8) -> i32 {
io::write_str(2, b"uclampset: only available on Linux\n");
1
}
#[cfg(test)]
mod tests {
extern crate std;
use std::process::Command;
use std::path::PathBuf;
fn get_armybox_path() -> PathBuf {
if let Ok(path) = std::env::var("ARMYBOX_PATH") {
return PathBuf::from(path);
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap());
let release = manifest_dir.join("target/release/armybox");
if release.exists() { return release; }
manifest_dir.join("target/debug/armybox")
}
#[test]
fn test_uclampset_help() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["uclampset", "-h"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(0));
let stdout = std::string::String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage"));
}
#[test]
fn test_uclampset_no_args() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["uclampset"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(1));
}
}