blob: a5cd3d618f4a0c23af37812ab343df5c0df88dd6 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fmt::Display;
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use crate::ProcessId;
use crate::ThreadId;
const CGROUP_CPU_PATH: &str = "/sys/fs/cgroup/cpu";
const CGROUP_CPUSET_PATH: &str = "/sys/fs/cgroup/cpuset";
const CPU_SHARE_FILE: &str = "cpu.shares";
const CPUS_FILE: &str = "cpus";
const CGROUP_PROCESSES_FILE: &str = "cgroup.procs";
const CGROUP_THREADS_FILE: &str = "tasks";
/// Error while setting up cgroups
#[derive(Debug)]
pub struct CgroupSetupError(PathBuf, io::Error);
/// Cgroup setup result returns [File].
pub type CgroupSetupResult = std::result::Result<File, CgroupSetupError>;
impl std::error::Error for CgroupSetupError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.1)
}
}
impl Display for CgroupSetupError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"failed setup cgroup: {:?}: {}",
self.0, self.1
))
}
}
/// Setup cpu cgroup
///
/// Cpu cgroups are used to control cpu share of managed processes.
///
/// This creates the cgroup if not exist.
///
/// This returns an opened [File] of "cgroup.procs" of the cgroup.
///
/// TODO(kawasin): Write unit test. The test requires an environment to execute
/// with cpu cgroup submodule enabled.
pub fn setup_cpu_cgroup(name: &str, cpu_shares: u16) -> CgroupSetupResult {
let cgroup_path = Path::new(CGROUP_CPU_PATH).join(name);
if !cgroup_path.exists() {
if let Err(e) = std::fs::create_dir_all(&cgroup_path) {
return Err(CgroupSetupError(cgroup_path, e));
}
}
let share_file_path = cgroup_path.join(CPU_SHARE_FILE);
std::fs::write(&share_file_path, cpu_shares.to_string())
.map_err(|e| CgroupSetupError(share_file_path, e))?;
let cgroup_file_path = cgroup_path.join(CGROUP_PROCESSES_FILE);
std::fs::OpenOptions::new()
.write(true)
.open(&cgroup_file_path)
.map_err(|e| CgroupSetupError(cgroup_file_path, e))
}
/// Setup cpuset cgroup
///
/// Cpuset cgroups are used to control cpu cores for managed threads.
///
/// This creates the cgroup if not exist.
///
/// This returns an opened [File] of "tasks" of the cgroup.
///
/// TODO(kawasin): Write unit test. The test requires an environment to execute
/// with cpuset cgroup submodule enabled.
pub fn setup_cpuset_cgroup(name: &str, cpus: &str) -> CgroupSetupResult {
let cgroup_path = Path::new(CGROUP_CPUSET_PATH).join(name);
if !cgroup_path.exists() {
if let Err(e) = std::fs::create_dir_all(&cgroup_path) {
return Err(CgroupSetupError(cgroup_path, e));
}
}
let cpus_file_path = cgroup_path.join(CPUS_FILE);
std::fs::write(&cpus_file_path, cpus).map_err(|e| CgroupSetupError(cpus_file_path, e))?;
let cgroup_file_path = cgroup_path.join(CGROUP_THREADS_FILE);
std::fs::OpenOptions::new()
.write(true)
.open(&cgroup_file_path)
.map_err(|e| CgroupSetupError(cgroup_file_path, e))
}
/// Set of cgroups used for scheduler settings.
///
/// cpu cgroups are used for [CpuCgroup]. The files must points "cgroup.procs"
/// file of each cpu cgroup.
///
/// cpuset cgroups are used for [CpusetCgroup]. The files must points "tasks"
/// file of each cpuset cgroup.
#[derive(Debug)]
pub struct CgroupContext {
/// cgroup.procs file of cpu cgroup for normal processes
pub cpu_normal: File,
/// cgroup.procs file of cpu cgroup for background processes
pub cpu_background: File,
/// tasks file of cpuset cgroup for threads using all CPU cores
pub cpuset_all: File,
/// tasks file of cpuset cgroup for threads using efficient CPU cores only
pub cpuset_efficient: File,
}
impl CgroupContext {
pub(crate) fn set_cpu_cgroup(
&mut self,
process_id: ProcessId,
cpu_cgroup: CpuCgroup,
) -> io::Result<()> {
let cgroup_file = match cpu_cgroup {
CpuCgroup::Normal => &mut self.cpu_normal,
CpuCgroup::Background => &mut self.cpu_background,
};
let _ = cgroup_file.write(process_id.0.to_string().as_bytes())?;
Ok(())
}
pub(crate) fn set_cpuset_cgroup(
&mut self,
thread_id: ThreadId,
cpuset_cgroup: CpusetCgroup,
) -> io::Result<()> {
let cgroup_file = match cpuset_cgroup {
CpusetCgroup::All => &mut self.cpuset_all,
CpusetCgroup::Efficient => &mut self.cpuset_efficient,
};
let _ = cgroup_file.write(thread_id.0.to_string().as_bytes())?;
Ok(())
}
}
/// Cpu cgroups
#[derive(Clone, Copy, Debug)]
pub enum CpuCgroup {
Normal,
Background,
}
impl CpuCgroup {
/// Name to display
pub fn name(&self) -> &'static str {
match self {
Self::Normal => "cpu.normal",
Self::Background => "cpu.background",
}
}
}
/// Cpuset cgroups
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum CpusetCgroup {
All,
Efficient,
}
impl CpusetCgroup {
/// Name to display
pub fn name(&self) -> &'static str {
match self {
Self::All => "cpuset.all",
Self::Efficient => "cpuset.efficient",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_set_cpu_cgroup() {
let (mut ctx, mut files) = create_fake_cgroup_context_pair();
ctx.set_cpu_cgroup(ProcessId(123), CpuCgroup::Normal)
.unwrap();
assert_eq!(read_number(&mut files.cpu_normal), Some(123));
ctx.set_cpu_cgroup(ProcessId(456), CpuCgroup::Normal)
.unwrap();
assert_eq!(read_number(&mut files.cpu_normal), Some(456));
ctx.set_cpu_cgroup(ProcessId(789), CpuCgroup::Background)
.unwrap();
assert_eq!(read_number(&mut files.cpu_normal), None);
assert_eq!(read_number(&mut files.cpu_background), Some(789));
}
#[test]
fn test_set_cpuset_cgroup() {
let (mut ctx, mut files) = create_fake_cgroup_context_pair();
ctx.set_cpuset_cgroup(ThreadId(123), CpusetCgroup::All)
.unwrap();
assert_eq!(read_number(&mut files.cpuset_all), Some(123));
ctx.set_cpuset_cgroup(ThreadId(456), CpusetCgroup::All)
.unwrap();
assert_eq!(read_number(&mut files.cpuset_all), Some(456));
ctx.set_cpuset_cgroup(ThreadId(789), CpusetCgroup::Efficient)
.unwrap();
assert_eq!(read_number(&mut files.cpuset_all), None);
assert_eq!(read_number(&mut files.cpuset_efficient), Some(789));
}
}