use std::collections::HashMap;
use std::fs::{self, OpenOptions};
use std::io::{BufRead, BufReader, ErrorKind, Write};
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use nix::unistd::Pid;
use oci_spec::runtime::LinuxIntelRdt;
use pathrs::flags::OpenFlags;
use pathrs::procfs::{ProcfsBase, ProcfsHandle};
use procfs::process::MountInfo;
use regex::Regex;
#[derive(Debug, thiserror::Error)]
pub enum IntelRdtError {
#[error(transparent)]
ProcError(#[from] procfs::ProcError),
#[error("failed to find resctrl mount point")]
ResctrlMountPointNotFound,
#[error("failed to find ID for resctrl")]
ResctrlIdNotFound,
#[error("existing schemata found but data did not match")]
ExistingSchemataMismatch,
#[error("failed to read existing schemata")]
ReadSchemata(#[source] std::io::Error),
#[error("failed to write schemata")]
WriteSchemata(#[source] std::io::Error),
#[error("failed to open schemata file")]
OpenSchemata(#[source] std::io::Error),
#[error(transparent)]
ParseLine(#[from] ParseLineError),
#[error("no resctrl subdirectory found for container id")]
NoResctrlSubdirectory,
#[error("failed to remove subdirectory")]
RemoveSubdirectory(#[source] std::io::Error),
#[error("no parent for resctrl subdirectory")]
NoResctrlSubdirectoryParent,
#[error("invalid resctrl directory")]
InvalidResctrlDirectory,
#[error("resctrl closID directory didn't exist")]
NoClosIDDirectory,
#[error("failed to write to resctrl closID directory")]
WriteClosIDTasksFile(#[source] std::io::Error),
#[error("failed to open resctrl closID directory")]
OpenClosIDTasksFile(#[source] std::io::Error),
#[error("failed to create resctrl closID directory")]
CreateClosIDDirectory(#[source] std::io::Error),
#[error("failed to write to resctrl monitoring tasks file")]
WriteMonitoringTasksFile(#[source] std::io::Error),
#[error("failed to open resctrl monitoring tasks file")]
OpenMonitoringTasksFile(#[source] std::io::Error),
#[error("failed to create resctrl monitoring directory")]
CreateMonitoringDirectory(#[source] std::io::Error),
#[error("failed to canonicalize path")]
Canonicalize(#[source] std::io::Error),
#[error(transparent)]
Pathrs(#[from] pathrs::error::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("failed to cleanup intel rdt: {0}")]
Cleanup(String),
}
#[derive(Debug, thiserror::Error)]
pub enum ParseLineError {
#[error("MB line doesn't match validation")]
MBLine,
#[error("MB token has wrong number of fields")]
MBToken,
#[error("L3 line doesn't match validation")]
L3Line,
#[error("L3 token has wrong number of fields")]
L3Token,
#[error("Generic line doesn't match validation")]
GenericLine,
#[error("Generic token has wrong number of fields")]
GenericToken,
}
type Result<T> = std::result::Result<T, IntelRdtError>;
pub fn delete_resctrl_subdirectory_by_id(id: &str) -> Result<()> {
let dir = find_resctrl_mount_point().map_err(|err| {
tracing::error!("failed to find resctrl mount point: {err}");
err
})?;
let path = dir.join(id);
let container_resctrl_path = match path.canonicalize() {
Ok(p) => p,
Err(err) if err.kind() == ErrorKind::NotFound => return Ok(()),
Err(err) => {
tracing::error!(?dir, ?path, "failed to canonicalize path: {err}");
return Err(IntelRdtError::Canonicalize(err));
}
};
match container_resctrl_path.parent() {
Some(parent) => {
if parent == dir && container_resctrl_path.exists() {
if let Err(err) = fs::remove_dir(&container_resctrl_path) {
if err.kind() != ErrorKind::NotFound {
tracing::error!(path = ?container_resctrl_path, "failed to remove resctrl subdirectory: {err}");
return Err(IntelRdtError::RemoveSubdirectory(err));
}
}
} else {
return Err(IntelRdtError::NoResctrlSubdirectory);
}
}
None => return Err(IntelRdtError::NoResctrlSubdirectoryParent),
}
Ok(())
}
pub fn cleanup_intel_rdt(
intel_rdt_dir: Option<&Path>,
intel_rdt_monitoring_dir: Option<&Path>,
clean_up_intel_rdt_subdirectory: Option<bool>,
id: &str,
) -> Result<()> {
let mut errors = Vec::new();
let delete_resctrl_subdirectory = |path: &Path| -> Result<()> {
if let Err(err) = fs::remove_dir(path) {
if err.kind() != ErrorKind::NotFound {
return Err(IntelRdtError::RemoveSubdirectory(err));
}
}
Ok(())
};
if let Some(path) = intel_rdt_monitoring_dir {
if let Err(e) = delete_resctrl_subdirectory(path) {
errors.push(format!("failed to delete monitoring directory: {e}"));
}
}
if let Some(path) = intel_rdt_dir {
if let Err(e) = delete_resctrl_subdirectory(path) {
errors.push(format!("failed to delete directory: {e}"));
}
} else if let Some(true) = clean_up_intel_rdt_subdirectory {
if let Err(e) = delete_resctrl_subdirectory_by_id(id) {
errors.push(format!("failed to delete directory by id: {}", e));
}
}
if !errors.is_empty() {
return Err(IntelRdtError::Cleanup(errors.join(";")));
}
Ok(())
}
pub fn find_resctrl_mount_point() -> Result<PathBuf> {
let reader = BufReader::new(ProcfsHandle::new()?.open(
ProcfsBase::ProcSelf,
"mountinfo",
OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,
)?);
for lr in reader.lines() {
let s = lr.map_err(IntelRdtError::from)?;
let mi = MountInfo::from_line(&s).map_err(IntelRdtError::from)?;
if mi.fs_type == "resctrl" {
let path = mi
.mount_point
.canonicalize()
.map_err(IntelRdtError::Canonicalize)?;
return Ok(path);
}
}
Err(IntelRdtError::ResctrlMountPointNotFound)
}
fn setup_resctrl_group(
resctrl_container_dir: &Path,
init_pid: Pid,
only_clos_id_set: bool,
) -> Result<bool> {
let mut created_dir = false;
if !resctrl_container_dir.exists() {
if only_clos_id_set {
return Err(IntelRdtError::NoClosIDDirectory);
}
fs::create_dir_all(resctrl_container_dir).map_err(|err| {
tracing::error!("failed to create resctrl subdirectory: {err}");
IntelRdtError::CreateClosIDDirectory(err)
})?;
created_dir = true;
}
write_pid_to_tasks(
resctrl_container_dir,
init_pid,
IntelRdtError::OpenClosIDTasksFile,
IntelRdtError::WriteClosIDTasksFile,
)?;
Ok(created_dir)
}
fn write_pid_to_tasks<F1, F2>(dir: &Path, pid: Pid, on_open_err: F1, on_write_err: F2) -> Result<()>
where
F1: FnOnce(std::io::Error) -> IntelRdtError,
F2: FnOnce(std::io::Error) -> IntelRdtError,
{
let tasks = dir.join("tasks");
let mut file = OpenOptions::new()
.write(true)
.open(tasks)
.map_err(on_open_err)?;
file.write_all(pid.to_string().as_bytes())
.map_err(on_write_err)?;
Ok(())
}
fn combine_l3_cache_and_mem_bw_schemas(
l3_cache_schema: &Option<String>,
mem_bw_schema: &Option<String>,
) -> Option<String> {
match (l3_cache_schema, mem_bw_schema) {
(Some(real_l3_cache_schema), Some(real_mem_bw_schema)) => {
let mut output: Vec<&str> = vec![];
for line in real_l3_cache_schema.lines() {
if line.starts_with("MB:") {
continue;
}
output.push(line);
}
output.push(real_mem_bw_schema);
Some(output.join("\n"))
}
(Some(_), None) => {
l3_cache_schema.to_owned()
}
(None, Some(_)) => mem_bw_schema.to_owned(),
(None, None) => None,
}
}
#[derive(PartialEq)]
enum LineType {
L3Line,
L3DataLine,
L3CodeLine,
MbLine,
Generic(String),
}
#[derive(PartialEq)]
struct ParsedLine {
line_type: LineType,
tokens: HashMap<String, String>,
}
fn parse_mb_line(line: &str) -> std::result::Result<HashMap<String, String>, ParseLineError> {
let mut token_map = HashMap::new();
static MB_VALIDATE_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^MB:(?:\s|;)*(?:\w+\s*=\s*\w+)?(?:(?:\s*;+\s*)+\w+\s*=\s*\w+)*(?:\s|;)*$")
.unwrap()
});
static MB_CAPTURE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(\w+)\s*=\s*(\w+)").unwrap());
if !MB_VALIDATE_RE.is_match(line) {
return Err(ParseLineError::MBLine);
}
for token in MB_CAPTURE_RE.captures_iter(line) {
match (token.get(1), token.get(2)) {
(Some(key), Some(value)) => {
token_map.insert(key.as_str().to_string(), value.as_str().to_string());
}
_ => return Err(ParseLineError::MBToken),
}
}
Ok(token_map)
}
fn parse_l3_line(line: &str) -> std::result::Result<HashMap<String, String>, ParseLineError> {
let mut token_map = HashMap::new();
static L3_VALIDATE_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?:L3|L3DATA|L3CODE):(?:\s|;)*(?:\w+\s*=\s*[[:xdigit:]]+)?(?:(?:\s*;+\s*)+\w+\s*=\s*[[:xdigit:]]+)*(?:\s|;)*$").unwrap()
});
static L3_CAPTURE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(\w+)\s*=\s*0*([[:xdigit:]]+)").unwrap());
if !L3_VALIDATE_RE.is_match(line) {
return Err(ParseLineError::L3Line);
}
for token in L3_CAPTURE_RE.captures_iter(line) {
match (token.get(1), token.get(2)) {
(Some(key), Some(value)) => {
token_map.insert(key.as_str().to_string(), value.as_str().to_string());
}
_ => return Err(ParseLineError::L3Token),
}
}
Ok(token_map)
}
fn parse_generic_line(line: &str) -> std::result::Result<HashMap<String, String>, ParseLineError> {
let mut token_map = HashMap::new();
static OTHER_VALIDATE_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[A-Za-z0-9]+:(?:\s|;)*(?:\w+\s*=\s*[[:xdigit:]]+)?(?:(?:\s*;+\s*)+\w+\s*=\s*[[:xdigit:]]+)*(?:\s|;)*$").unwrap()
});
static OTHER_CAPTURE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(\w+)\s*=\s*([[:xdigit:]]+)").unwrap());
if !OTHER_VALIDATE_RE.is_match(line) {
return Err(ParseLineError::GenericLine);
}
for token in OTHER_CAPTURE_RE.captures_iter(line) {
match (token.get(1), token.get(2)) {
(Some(key), Some(value)) => {
let val_str = value.as_str().trim_start_matches('0');
let final_val = if val_str.is_empty() { "0" } else { val_str };
token_map.insert(key.as_str().to_string(), final_val.to_string());
}
_ => return Err(ParseLineError::GenericToken),
}
}
Ok(token_map)
}
fn get_line_type(line: &str) -> LineType {
if line.starts_with("L3:") {
return LineType::L3Line;
}
if line.starts_with("L3CODE:") {
return LineType::L3CodeLine;
}
if line.starts_with("L3DATA:") {
return LineType::L3DataLine;
}
if line.starts_with("MB:") {
return LineType::MbLine;
}
if let Some(pos) = line.find(':') {
let prefix = &line[..pos];
if prefix.chars().all(|c| c.is_alphanumeric() || c == '_') {
return LineType::Generic(prefix.to_string());
}
}
LineType::Generic(String::new())
}
fn parse_line(line: &str) -> Option<std::result::Result<ParsedLine, ParseLineError>> {
let line_type = get_line_type(line);
let maybe_tokens = match &line_type {
LineType::L3Line => parse_l3_line(line).map(Some),
LineType::L3DataLine => parse_l3_line(line).map(Some),
LineType::L3CodeLine => parse_l3_line(line).map(Some),
LineType::MbLine => parse_mb_line(line).map(Some),
LineType::Generic(prefix) => {
if prefix.is_empty() {
Ok(None)
} else {
parse_generic_line(line).map(Some)
}
}
};
match maybe_tokens {
Err(err) => Some(Err(err)),
Ok(None) => None,
Ok(Some(tokens)) => Some(Ok(ParsedLine { line_type, tokens })),
}
}
fn compare_lines(first_lines: &[ParsedLine], second_lines: &[ParsedLine]) -> bool {
first_lines.iter().all(|line| second_lines.contains(line))
&& second_lines.iter().all(|line| first_lines.contains(line))
}
fn is_same_schema(combined_schema: &str, existing_schema: &str) -> Result<bool> {
let combined = combined_schema
.lines()
.filter_map(parse_line)
.collect::<std::result::Result<Vec<ParsedLine>, _>>()?;
let existing = existing_schema
.lines()
.filter_map(parse_line)
.collect::<std::result::Result<Vec<ParsedLine>, _>>()?;
Ok(compare_lines(&combined, &existing))
}
fn get_schemata_data(intel_rdt: &LinuxIntelRdt) -> Option<String> {
let legacy_schemata =
combine_l3_cache_and_mem_bw_schemas(intel_rdt.l3_cache_schema(), intel_rdt.mem_bw_schema());
if let Some(schemata) = intel_rdt.schemata() {
if !schemata.is_empty() {
let modern_schemata = schemata.join("\n");
if let Some(legacy) = legacy_schemata {
return Some(format!("{}\n{}", legacy, modern_schemata));
}
return Some(modern_schemata);
}
}
legacy_schemata
}
fn write_resctrl_schemata(
path: &Path,
id: &str,
intel_rdt: &LinuxIntelRdt,
clos_id_was_set: bool,
created_dir: bool,
) -> Result<()> {
let schemata = path.to_owned().join(id).join("schemata");
let maybe_combined_schema = get_schemata_data(intel_rdt);
if let Some(combined_schema) = maybe_combined_schema {
if clos_id_was_set && !created_dir {
let data = fs::read_to_string(&schemata).map_err(IntelRdtError::ReadSchemata)?;
if !is_same_schema(&combined_schema, &data)? {
Err(IntelRdtError::ExistingSchemataMismatch)?;
}
} else {
let mut file = OpenOptions::new()
.truncate(true)
.write(true)
.open(schemata)
.map_err(IntelRdtError::OpenSchemata)?;
let schema_with_newline = combined_schema + "\n";
write!(file, "{schema_with_newline}").map_err(IntelRdtError::WriteSchemata)?;
}
}
Ok(())
}
pub fn setup_intel_rdt(
maybe_container_id: Option<&str>,
init_pid: &Pid,
intel_rdt: &LinuxIntelRdt,
) -> Result<(Option<PathBuf>, Option<PathBuf>)> {
let mount_point = find_resctrl_mount_point().inspect_err(|_err| {
tracing::error!("failed to find a mounted resctrl file system");
})?;
let container_id = maybe_container_id.ok_or(IntelRdtError::ResctrlIdNotFound)?;
let clos_id_set = intel_rdt.clos_id().is_some();
let id = intel_rdt.clos_id().as_deref().unwrap_or(container_id);
let has_schemata = intel_rdt.l3_cache_schema().is_some()
|| intel_rdt.mem_bw_schema().is_some()
|| intel_rdt.schemata().as_ref().is_some_and(|s| !s.is_empty());
let only_clos_id_set = clos_id_set && !has_schemata;
let container_dir = mount_point.join(id);
let created_dir = setup_resctrl_group(&container_dir, *init_pid, only_clos_id_set)?;
write_resctrl_schemata(&mount_point, id, intel_rdt, clos_id_set, created_dir).inspect_err(
|_err| {
tracing::error!("failed to write schemata to resctrl schemata file");
},
)?;
let mut created_monitoring_dir = None;
if intel_rdt.enable_monitoring().unwrap_or(false) {
let mon_dir = container_dir.join("mon_groups").join(container_id);
if !mon_dir.exists() {
fs::create_dir_all(&mon_dir).map_err(|err| {
tracing::error!("failed to create resctrl monitoring subdirectory: {err}");
IntelRdtError::CreateMonitoringDirectory(err)
})?;
}
write_pid_to_tasks(
&mon_dir,
*init_pid,
IntelRdtError::OpenMonitoringTasksFile,
IntelRdtError::WriteMonitoringTasksFile,
)?;
created_monitoring_dir = Some(mon_dir);
}
let need_to_delete_directory = (!clos_id_set && created_dir).then_some(container_dir);
Ok((need_to_delete_directory, created_monitoring_dir))
}
#[cfg(test)]
mod test {
use std::fs;
use anyhow::Result;
use super::*;
#[test]
fn test_combine_schemas() -> Result<()> {
let res = combine_l3_cache_and_mem_bw_schemas(&None, &None);
assert!(res.is_none());
let l3_1 = "L3:0=f;1=f0";
let bw_1 = "MB:0=70;1=20";
let res = combine_l3_cache_and_mem_bw_schemas(&Some(l3_1.to_owned()), &None);
assert!(res.is_some());
assert!(res.unwrap() == "L3:0=f;1=f0");
let res = combine_l3_cache_and_mem_bw_schemas(&None, &Some(bw_1.to_owned()));
assert!(res.is_some());
assert!(res.unwrap() == "MB:0=70;1=20");
let res =
combine_l3_cache_and_mem_bw_schemas(&Some(l3_1.to_owned()), &Some(bw_1.to_owned()));
assert!(res.is_some());
let val = res.unwrap();
assert!(val.lines().any(|line| line == "MB:0=70;1=20"));
assert!(val.lines().any(|line| line == "L3:0=f;1=f0"));
let l3_2 = "L3:0=f;1=f0\nL3:2=f\n;MB:0=20;1=70";
let res =
combine_l3_cache_and_mem_bw_schemas(&Some(l3_2.to_owned()), &Some(bw_1.to_owned()));
assert!(res.is_some());
let val = res.unwrap();
assert!(val.lines().any(|line| line == "MB:0=70;1=20"));
assert!(val.lines().any(|line| line == "L3:0=f;1=f0"));
assert!(val.lines().any(|line| line == "L3:2=f"));
assert!(!val.lines().any(|line| line == "MB:0=20;1=70"));
let l3_generic = "L3:0=f;1=f0\nL2:0=f\nMB:0=20;1=70";
let res = combine_l3_cache_and_mem_bw_schemas(
&Some(l3_generic.to_owned()),
&Some(bw_1.to_owned()),
);
assert!(res.is_some());
let val = res.unwrap();
assert!(val.lines().any(|line| line == "L2:0=f"));
assert!(!val.lines().any(|line| line == "MB:0=20;1=70"));
let l3_messy_mb = "L3:0=f\nMB: 0=10; 1=20 ;;";
let res = combine_l3_cache_and_mem_bw_schemas(
&Some(l3_messy_mb.to_owned()),
&Some(bw_1.to_owned()),
);
assert!(res.is_some());
let val = res.unwrap();
assert!(val.lines().any(|line| line == "L3:0=f"));
assert!(!val.lines().any(|line| line.starts_with("MB: 0=10")));
assert!(val.lines().any(|line| line == bw_1));
Ok(())
}
#[test]
fn test_is_same_schema() -> Result<()> {
assert!(is_same_schema("L3:0=f;1=f0", "L3:0=f;1=f0")?);
assert!(is_same_schema("L3DATA:0=f;1=f0", "L3DATA:0=f;1=f0")?);
assert!(is_same_schema("L3CODE:0=f;1=f0", "L3CODE:0=f;1=f0")?);
assert!(is_same_schema("MB:0=bar;1=f0", "MB:0=bar;1=f0")?);
assert!(is_same_schema("L3:", "L3:")?);
assert!(is_same_schema("MB:", "MB:")?);
assert!(is_same_schema("L2:0=f;1=f0", "L2:0=f;1=f0")?);
assert!(is_same_schema("SMBA:0=20", "SMBA:0=20")?);
assert!(!is_same_schema("L3:0=f;1=f0", "L3:2=f")?);
assert!(!is_same_schema("MB:0=bar;1=f0", "MB:0=foo;1=f0")?);
assert!(!is_same_schema("L3DATA:0=f;1=f0", "L3CODE:2=f")?);
assert!(!is_same_schema("L3DATA:0=f;1=f0", "L3CODE:2=f")?);
assert!(!is_same_schema("L3DATA:0=f", "L3CODE:0=f")?);
assert!(!is_same_schema("L3:0=f", "L3DATA:0=f")?);
assert!(!is_same_schema("L3CODE:0=f", "L3:0=f")?);
assert!(!is_same_schema("MB:0=f", "L3:0=f")?);
assert!(!is_same_schema("L2:0=f", "L3:0=f")?);
assert!(!is_same_schema("L2:0=f;1=f0", "L2:0=ff;1=f0")?);
assert!(!is_same_schema("SMBA:0=20", "SMBA:0=30")?);
assert!(!is_same_schema("SMBA:0=20", "MBA:0=20")?);
assert!(is_same_schema(
"L3:0=f;1=f0\nL3:2=f",
"L3:0=f;1=f0\nL3:2=f"
)?);
assert!(is_same_schema("L3:0=f;1=f0\nL3:2=f\nBAR:foo", "L3:0=f;1=f0\nL3:2=f").is_err());
assert!(!is_same_schema(
"L3:0=f;1=f0\nL3:2=f\nL3:3=f",
"L3:0=f;1=f0\nL3:2=f"
)?);
assert!(!is_same_schema(
"L3:0=f;1=f0\nL3:2=f\nL3:3=f",
"L3:0=f;1=f0\nL3:2=f"
)?);
assert!(!is_same_schema(
"L3:0=f;1=f0\nL3:2=f",
"L3:0=f;1=f0\nL3:2=f\nL3:3=f"
)?);
assert!(is_same_schema("L3:1=f0;0=0", "L3:0=0;1=f0")?);
assert!(is_same_schema("L2:1=f0;0=f", "L2:0=f;1=f0")?);
assert!(is_same_schema("L3:;; 0 = f; ; 1=f0", "L3:0=f;1 = f0;;")?);
assert!(is_same_schema("L2:;; 0 = f; ; 1=f0", "L2:0=f;1 = f0;;")?);
assert!(is_same_schema("L2:0=f;1=f0;", "L2:0=f;1=f0")?);
assert!(is_same_schema("L2: 0 = ff ", "L2:0=ff")?);
assert!(is_same_schema("L3:0=000f", "L3:0=0f")?);
assert!(is_same_schema("L3:0=000f", "L3:0=0f")?);
assert!(is_same_schema("L3:0=f", "L3:0=0f")?);
assert!(is_same_schema("L3:0=0", "L3:0=0000")?);
assert!(is_same_schema("L2:0=00ff;1=000f0", "L2:0=ff;1=f0")?);
assert!(is_same_schema("L3:1=;0=f", "L3:1=;0=f").is_err());
assert!(is_same_schema("L3:=0;0=f", "L3:=0;0=f").is_err());
assert!(is_same_schema("L3:1=0=3;0=f", "L3:1=0=3;0=f").is_err());
assert!(is_same_schema("L3:1=bar", "L3:1=bar").is_err());
assert!(is_same_schema("MB:1=;0=f", "MB:1=;0=f").is_err());
assert!(is_same_schema("MB:=0;0=f", "MB:=0;0=f").is_err());
assert!(is_same_schema("MB:1=0=3;0=f", "MB:1=0=3;0=f").is_err());
assert!(is_same_schema("L2:0=invalid_hex_string", "L2:0=invalid_hex_string").is_err());
assert!(
is_same_schema(
"L2:0=0123456789abcdef0123456789abcdef0123456789abcdefzz",
"L2:0=0123456789abcdef0123456789abcdef0123456789abcdefzz"
)
.is_err()
);
assert!(is_same_schema("L2:0=00ff;1=000f0", "L2:0=ff;1=f0")?);
assert!(is_same_schema("L2:0=0;1=00", "L2:0=0;1=0")?);
assert!(is_same_schema(
"L3:0=f;1=f0\n\nL2:0=f\n",
"L3:0=f;1=f0\nL2:0=f"
)?);
Ok(())
}
#[test]
fn test_get_line_type() {
assert!(matches!(get_line_type("L3:0=f"), LineType::L3Line));
assert!(matches!(get_line_type("L3DATA:0=f"), LineType::L3DataLine));
assert!(matches!(get_line_type("L3CODE:0=f"), LineType::L3CodeLine));
assert!(matches!(get_line_type("MB:0=70"), LineType::MbLine));
let generic_l2 = get_line_type("L2:0=f");
if let LineType::Generic(prefix) = generic_l2 {
assert_eq!(prefix, "L2");
} else {
panic!("Expected LineType::Generic");
}
let generic_smba = get_line_type("SMBA:0=20");
if let LineType::Generic(prefix) = generic_smba {
assert_eq!(prefix, "SMBA");
} else {
panic!("Expected LineType::Generic");
}
}
#[test]
fn test_parse_generic_line() -> Result<()> {
let parsed = parse_generic_line("L2:0=00ff;1=f0")?;
assert_eq!(parsed.get("0").unwrap(), "ff");
assert_eq!(parsed.get("1").unwrap(), "f0");
let parsed_zero = parse_generic_line("L2:0=0000;1=00")?;
assert_eq!(parsed_zero.get("0").unwrap(), "0");
assert_eq!(parsed_zero.get("1").unwrap(), "0");
assert!(parse_generic_line("L2:0=;1=f0").is_err());
assert!(parse_generic_line("L2:0=invalid_hex").is_err());
Ok(())
}
#[test]
fn test_get_schemata_data() {
use oci_spec::runtime::LinuxIntelRdtBuilder;
let rdt_modern = LinuxIntelRdtBuilder::default()
.l3_cache_schema("L3:0=f;1=f0".to_owned())
.mem_bw_schema("MB:0=70;1=20".to_owned())
.schemata(vec!["L2:0=f;1=f0".to_owned(), "SMBA:0=20".to_owned()])
.build()
.unwrap();
let combined_modern = get_schemata_data(&rdt_modern).unwrap();
assert_eq!(
combined_modern,
"L3:0=f;1=f0\nMB:0=70;1=20\nL2:0=f;1=f0\nSMBA:0=20",
);
}
#[test]
fn test_setup_resctrl_group() -> Result<()> {
let tmp = tempfile::tempdir().unwrap();
let create_mock_group = |path: &std::path::Path| {
fs::create_dir_all(path).unwrap();
fs::File::create(path.join("tasks")).unwrap();
fs::File::create(path.join("schemata")).unwrap();
};
let container_dir = tmp.path().join("foo");
create_mock_group(&container_dir);
let res = setup_resctrl_group(&container_dir, Pid::from_raw(1000), false);
assert!(!res.unwrap()); let res = fs::read_to_string(container_dir.join("tasks"));
assert!(res.unwrap() == "1000");
let res = setup_resctrl_group(&container_dir, Pid::from_raw(1500), false);
assert!(!res.unwrap());
let foobar_dir = tmp.path().join("foobar");
let res = setup_resctrl_group(&foobar_dir, Pid::from_raw(2000), true);
assert!(res.is_err());
create_mock_group(&foobar_dir);
let res = setup_resctrl_group(&foobar_dir, Pid::from_raw(2500), true);
assert!(!res.unwrap());
Ok(())
}
#[test]
fn test_write_resctrl_schemata() -> Result<()> {
use oci_spec::runtime::LinuxIntelRdtBuilder;
let tmp = tempfile::tempdir().unwrap();
let foobar_dir = tmp.path().join("foobar");
let create_mock_group = |path: &Path| {
fs::create_dir_all(path).unwrap();
fs::File::create(path.join("tasks")).unwrap();
fs::File::create(path.join("schemata")).unwrap();
};
create_mock_group(&foobar_dir);
let res = setup_resctrl_group(&foobar_dir, Pid::from_raw(1000), false);
assert!(!res.unwrap());
let empty_rdt = LinuxIntelRdtBuilder::default().build().unwrap();
let res = write_resctrl_schemata(tmp.path(), "foobar", &empty_rdt, false, true);
assert!(res.is_ok());
let res = fs::read_to_string(tmp.path().join("foobar").join("schemata"));
assert!(res.unwrap().is_empty());
let l3_1 = "L3:0=f;1=f0\nL3:2=f\nMB:0=20;1=70";
let bw_1 = "MB:0=70;1=20";
let rdt_combined = LinuxIntelRdtBuilder::default()
.l3_cache_schema(l3_1.to_owned())
.mem_bw_schema(bw_1.to_owned())
.build()
.unwrap();
let res = write_resctrl_schemata(tmp.path(), "foobar", &rdt_combined, false, true);
assert!(res.is_ok());
let res = fs::read_to_string(tmp.path().join("foobar").join("schemata"));
assert!(res.is_ok());
assert!(is_same_schema(
"L3:0=f;1=f0\nL3:2=f\nMB:0=70;1=20\n",
&res.unwrap()
)?);
let res = write_resctrl_schemata(tmp.path(), "foobar", &rdt_combined, true, false);
assert!(res.is_ok());
let l3_2 = "L3:0=f;1=f0\nMB:0=20;1=70";
let bw_2 = "MB:0=70;1=20";
let rdt_different = LinuxIntelRdtBuilder::default()
.l3_cache_schema(l3_2.to_owned())
.mem_bw_schema(bw_2.to_owned())
.build()
.unwrap();
let res = write_resctrl_schemata(tmp.path(), "foobar", &rdt_different, true, false);
assert!(res.is_err());
let rdt_schemata = LinuxIntelRdtBuilder::default()
.l3_cache_schema(l3_1.to_owned())
.mem_bw_schema(bw_1.to_owned())
.schemata(vec!["L2:0=f;1=f0".to_owned()])
.build()
.unwrap();
let foobar_modern_dir = tmp.path().join("foobar_modern");
create_mock_group(&foobar_modern_dir);
let _ = setup_resctrl_group(&foobar_modern_dir, Pid::from_raw(1001), false);
let res = write_resctrl_schemata(tmp.path(), "foobar_modern", &rdt_schemata, false, true);
assert!(res.is_ok());
let written_data =
fs::read_to_string(tmp.path().join("foobar_modern").join("schemata")).unwrap();
assert_eq!(
written_data,
"L3:0=f;1=f0\nL3:2=f\nMB:0=70;1=20\nL2:0=f;1=f0\n"
);
Ok(())
}
#[test]
fn test_cleanup_intel_rdt() -> Result<()> {
let tmp = tempfile::tempdir().unwrap();
let mon_dir = tmp.path().join("mon_groups").join("test_container");
fs::create_dir_all(&mon_dir)?;
let res = cleanup_intel_rdt(None, Some(&mon_dir), None, "test_container");
assert!(res.is_ok());
assert!(!mon_dir.exists());
let rdt_dir = tmp.path().join("test_container");
fs::create_dir_all(&rdt_dir)?;
let res = cleanup_intel_rdt(Some(&rdt_dir), None, None, "test_container");
assert!(res.is_ok());
assert!(!rdt_dir.exists());
let res = cleanup_intel_rdt(None, None, Some(true), "test_container");
assert!(res.is_err());
let err_str = res.unwrap_err().to_string();
assert!(err_str.contains("failed to find resctrl mount point"));
Ok(())
}
}