| // 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)); | |
| } | |
| } |