diff --git a/.github/workflows/benchmark_execution_time.yml b/.github/workflows/benchmark_execution_time.yml index bbec725fa..87b177b6c 100644 --- a/.github/workflows/benchmark_execution_time.yml +++ b/.github/workflows/benchmark_execution_time.yml @@ -7,7 +7,7 @@ on: jobs: building-pr-branch: if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github easy-benchmark' - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 steps: @@ -37,7 +37,7 @@ jobs: building-main-branch: if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github easy-benchmark' - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 steps: @@ -72,7 +72,7 @@ jobs: needs: - building-pr-branch - building-main-branch - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 steps: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index c4659943d..5b1e0c781 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -5,7 +5,7 @@ on: jobs: changes: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 outputs: dirs: ${{ steps.filter.outputs.changes }} @@ -19,7 +19,7 @@ jobs: deploy: needs: [changes] if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/integration_tests_validation.yaml b/.github/workflows/integration_tests_validation.yaml index 773191b3b..72b499599 100644 --- a/.github/workflows/integration_tests_validation.yaml +++ b/.github/workflows/integration_tests_validation.yaml @@ -8,7 +8,7 @@ on: jobs: changes: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 outputs: dirs: ${{ steps.filter.outputs.changes }} @@ -22,7 +22,7 @@ jobs: validate: needs: [changes] if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: matrix: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4eb70a759..8fbb89c5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: jobs: changes: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 outputs: dirs: ${{ steps.filter.outputs.changes }} @@ -27,7 +27,7 @@ jobs: check: needs: [changes] if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: matrix: @@ -51,7 +51,7 @@ jobs: working-directory: ${{matrix.dirs}} run: cargo clippy --all-targets --all-features -- -D warnings tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: matrix: @@ -71,7 +71,7 @@ jobs: export LD_LIBRARY_PATH=$HOME/.wasmedge/lib cd ./crates && cargo test --all --all-features --no-fail-fast coverage: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 name: Run test coverage steps: @@ -103,7 +103,7 @@ jobs: with: file: ./coverage.lcov integration_tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: matrix: diff --git a/.github/workflows/podman_tests.yaml b/.github/workflows/podman_tests.yaml index 3d87c1932..77735ebeb 100644 --- a/.github/workflows/podman_tests.yaml +++ b/.github/workflows/podman_tests.yaml @@ -4,7 +4,7 @@ on: jobs: podman-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - run: sudo apt-get -y update diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2a437d20..e98c0819f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: check: name: Check - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 with: @@ -27,7 +27,7 @@ jobs: upload: name: Upload needs: check - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - run: sudo apt-get -y update @@ -54,7 +54,7 @@ jobs: release: name: Create Draft Release - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - upload outputs: @@ -101,7 +101,7 @@ jobs: publish: name: Publish Packages needs: check - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 env: CARGO_REGISTRY_TOKEN: 'DUMMY Token' #${{ secrets.CARGO_REGISTRY_TOKEN }} steps: diff --git a/Cargo.lock b/Cargo.lock index bdf806698..47d6d59e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1947,11 +1947,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] @@ -2203,9 +2202,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] @@ -2221,9 +2220,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -3277,6 +3276,7 @@ name = "youki" version = "0.0.4" dependencies = [ "anyhow", + "caps", "chrono", "clap", "clap_complete", diff --git a/crates/libcgroups/src/stats.rs b/crates/libcgroups/src/stats.rs index 6b906bd7c..6d53065b2 100644 --- a/crates/libcgroups/src/stats.rs +++ b/crates/libcgroups/src/stats.rs @@ -32,6 +32,8 @@ pub struct CpuStats { pub usage: CpuUsage, /// Cpu Throttling statistics for the cgroup pub throttling: CpuThrottling, + /// Pressure Stall Information + pub psi: PSIStats, } /// Reports the cpu usage for a cgroup @@ -79,6 +81,8 @@ pub struct MemoryStats { pub hierarchy: bool, /// Various memory statistics pub stats: HashMap, + /// Pressure Stall Information + pub psi: PSIStats, } /// Reports memory stats for one type of memory @@ -104,7 +108,7 @@ pub struct PidStats { } /// Reports block io stats for a cgroup -#[derive(Debug, Default, PartialEq, Eq, Serialize)] +#[derive(Debug, Default, PartialEq, Serialize)] pub struct BlkioStats { // Number of bytes transferred to/from a device by the cgroup pub service_bytes: Vec, @@ -122,6 +126,8 @@ pub struct BlkioStats { pub queued: Vec, // Number of requests merged into requests for I/O operations pub merged: Vec, + /// Pressure Stall Information + pub psi: PSIStats, } /// Reports single stat value for a specific device @@ -162,6 +168,26 @@ pub struct HugeTlbStats { pub fail_count: u64, } +/// Reports Pressure Stall Information for a cgroup +#[derive(Debug, Default, PartialEq, Serialize)] +pub struct PSIStats { + /// Percentage of walltime that some (one or more) tasks were delayed due to lack of resources + pub some: PSIData, + /// Percentage of walltime in which all tasks were delayed by lack of resources + pub full: PSIData, +} + +/// +#[derive(Debug, Default, PartialEq, Serialize)] +pub struct PSIData { + /// Running average over the last 10 seconds + pub avg10: f64, + /// Running average over the last 60 seconds + pub avg60: f64, + /// Running average over the last 300 seconds + pub avg300: f64, +} + /// Reports which hugepage sizes are supported by the system pub fn supported_page_sizes() -> Result> { let mut sizes = Vec::new(); @@ -327,6 +353,48 @@ pub fn pid_stats(cgroup_path: &Path) -> Result { Ok(stats) } +pub fn psi_stats(psi_file: &Path) -> Result { + let mut stats = PSIStats::default(); + + let psi = common::read_cgroup_file(psi_file)?; + for line in psi.lines() { + match &line[0..4] { + "some" => stats.some = parse_psi(&line[4..])?, + "full" => stats.full = parse_psi(&line[4..])?, + _ => continue, + } + } + + Ok(stats) +} + +fn parse_psi(stat_line: &str) -> Result { + let mut psi_data = PSIData::default(); + + for kv in stat_line.split_ascii_whitespace() { + match kv.split_once('=') { + Some(("avg10", v)) => { + psi_data.avg10 = v + .parse() + .with_context(|| format!("invalid psi value {v}"))? + } + Some(("avg60", v)) => { + psi_data.avg60 = v + .parse() + .with_context(|| format!("invalid psi value {v}"))? + } + Some(("avg300", v)) => { + psi_data.avg300 = v + .parse() + .with_context(|| format!("invalid psi value {v}"))? + } + _ => continue, + } + } + + Ok(psi_data) +} + #[cfg(test)] mod tests { use crate::test::{create_temp_dir, set_fixture}; @@ -520,4 +588,52 @@ mod tests { let result = parse_device_number("a:b"); assert!(result.is_err()); } + + #[test] + fn test_parse_psi_full_stats() { + let tmp = create_temp_dir("test_parse_psi_full_stats").unwrap(); + let file_content = [ + "some avg10=80.00 avg60=50.00 avg300=90.00 total=0", + "full avg10=10.00 avg60=30.00 avg300=50.00 total=0", + ] + .join("\n"); + let psi_file = set_fixture(&tmp, "psi.pressure", &file_content).unwrap(); + + let result = psi_stats(&psi_file).unwrap(); + assert_eq!( + result, + PSIStats { + some: PSIData { + avg10: 80.0, + avg60: 50.0, + avg300: 90.0 + }, + full: PSIData { + avg10: 10.0, + avg60: 30.0, + avg300: 50.0 + }, + } + ) + } + + #[test] + fn test_parse_psi_only_some() { + let tmp = create_temp_dir("test_parse_psi_only_some").unwrap(); + let file_content = ["some avg10=80.00 avg60=50.00 avg300=90.00 total=0"].join("\n"); + let psi_file = set_fixture(&tmp, "psi.pressure", &file_content).unwrap(); + + let result = psi_stats(&psi_file).unwrap(); + assert_eq!( + result, + PSIStats { + some: PSIData { + avg10: 80.0, + avg60: 50.0, + avg300: 90.0 + }, + full: PSIData::default(), + } + ) + } } diff --git a/crates/libcgroups/src/v1/blkio.rs b/crates/libcgroups/src/v1/blkio.rs index 18e0ee15a..06f482746 100644 --- a/crates/libcgroups/src/v1/blkio.rs +++ b/crates/libcgroups/src/v1/blkio.rs @@ -181,6 +181,7 @@ impl Blkio { wait_time: Self::parse_blkio_file(&cgroup_path.join(BLKIO_WAIT_TIME))?, queued: Self::parse_blkio_file(&cgroup_path.join(BLKIO_QUEUED))?, merged: Self::parse_blkio_file(&cgroup_path.join(BLKIO_MERGED))?, + ..Default::default() }; Ok(stats) diff --git a/crates/libcgroups/src/v1/memory.rs b/crates/libcgroups/src/v1/memory.rs index a1e74108a..c13145c2a 100644 --- a/crates/libcgroups/src/v1/memory.rs +++ b/crates/libcgroups/src/v1/memory.rs @@ -125,6 +125,7 @@ impl StatsProvider for Memory { cache: stats["cache"], hierarchy, stats, + ..Default::default() }) } } diff --git a/crates/libcgroups/src/v2/cpu.rs b/crates/libcgroups/src/v2/cpu.rs index 93e63eadc..a94ef487f 100644 --- a/crates/libcgroups/src/v2/cpu.rs +++ b/crates/libcgroups/src/v2/cpu.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, path::Path}; use crate::{ common::{self, ControllerOpt}, - stats::{CpuUsage, StatsProvider}, + stats::{self, CpuStats, StatsProvider}, }; use oci_spec::runtime::LinuxCpu; @@ -18,6 +18,7 @@ const UNRESTRICTED_QUOTA: &str = "max"; const MAX_CPU_WEIGHT: u64 = 10000; const CPU_STAT: &str = "cpu.stat"; +const CPU_PSI: &str = "cpu.pressure"; pub struct Cpu {} @@ -32,10 +33,10 @@ impl Controller for Cpu { } impl StatsProvider for Cpu { - type Stats = CpuUsage; + type Stats = CpuStats; fn stats(cgroup_path: &Path) -> Result { - let mut stats = CpuUsage::default(); + let mut stats = CpuStats::default(); let stat_content = common::read_cgroup_file(cgroup_path.join(CPU_STAT))?; for entry in stat_content.lines() { @@ -46,13 +47,15 @@ impl StatsProvider for Cpu { let value = parts[1].parse()?; match parts[0] { - "usage_usec" => stats.usage_total = value, - "user_usec" => stats.usage_user = value, - "system_usec" => stats.usage_kernel = value, + "usage_usec" => stats.usage.usage_total = value, + "user_usec" => stats.usage.usage_user = value, + "system_usec" => stats.usage.usage_kernel = value, _ => continue, } } + stats.psi = + stats::psi_stats(&cgroup_path.join(CPU_PSI)).context("could not read cpu psi")?; Ok(stats) } } @@ -137,7 +140,10 @@ impl Cpu { #[cfg(test)] mod tests { use super::*; - use crate::test::{create_temp_dir, set_fixture, setup}; + use crate::{ + stats::CpuUsage, + test::{create_temp_dir, set_fixture, setup}, + }; use oci_spec::runtime::LinuxCpuBuilder; use std::fs; @@ -300,6 +306,7 @@ mod tests { let tmp = create_temp_dir("test_stat_usage").expect("create temp directory for test"); let content = ["usage_usec 7730", "user_usec 4387", "system_usec 3498"].join("\n"); set_fixture(&tmp, CPU_STAT, &content).expect("create stat file"); + set_fixture(&tmp, CPU_PSI, "").expect("create psi file"); let actual = Cpu::stats(&tmp).expect("get cgroup stats"); let expected = CpuUsage { @@ -309,7 +316,7 @@ mod tests { ..Default::default() }; - assert_eq!(actual, expected); + assert_eq!(actual.usage, expected); } #[test] diff --git a/crates/libcgroups/src/v2/io.rs b/crates/libcgroups/src/v2/io.rs index 4ce9a2a5c..cc334676e 100644 --- a/crates/libcgroups/src/v2/io.rs +++ b/crates/libcgroups/src/v2/io.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result}; use crate::{ common::{self, ControllerOpt}, - stats::{self, BlkioDeviceStat, BlkioStats, StatsProvider}, + stats::{self, psi_stats, BlkioDeviceStat, BlkioStats, StatsProvider}, }; use super::controller::Controller; @@ -13,6 +13,7 @@ use oci_spec::runtime::LinuxBlockIo; const CGROUP_BFQ_IO_WEIGHT: &str = "io.bfq.weight"; const CGROUP_IO_WEIGHT: &str = "io.weight"; const CGROUP_IO_STAT: &str = "io.stat"; +const CGROUP_IO_PSI: &str = "io.pressure"; pub struct Io {} @@ -71,6 +72,7 @@ impl StatsProvider for Io { let stats = BlkioStats { service_bytes, serviced, + psi: psi_stats(&cgroup_path.join(CGROUP_IO_PSI)).context("could not read io psi")?, ..Default::default() }; @@ -315,6 +317,7 @@ mod test { ] .join("\n"); set_fixture(&tmp, "io.stat", &stat_content).unwrap(); + set_fixture(&tmp, CGROUP_IO_PSI, "").expect("create psi file"); let mut actual = Io::stats(&tmp).expect("get cgroup stats"); let expected = BlkioStats { diff --git a/crates/libcgroups/src/v2/manager.rs b/crates/libcgroups/src/v2/manager.rs index d571d1728..5101f2972 100644 --- a/crates/libcgroups/src/v2/manager.rs +++ b/crates/libcgroups/src/v2/manager.rs @@ -164,7 +164,7 @@ impl CgroupManager for Manager { for subsystem in CONTROLLER_TYPES { match subsystem { - ControllerType::Cpu => stats.cpu.usage = Cpu::stats(&self.full_path)?, + ControllerType::Cpu => stats.cpu = Cpu::stats(&self.full_path)?, ControllerType::HugeTlb => stats.hugetlb = HugeTlb::stats(&self.full_path)?, ControllerType::Pids => stats.pids = Pids::stats(&self.full_path)?, ControllerType::Memory => stats.memory = Memory::stats(&self.full_path)?, diff --git a/crates/libcgroups/src/v2/memory.rs b/crates/libcgroups/src/v2/memory.rs index 29c18a820..f9082f22c 100644 --- a/crates/libcgroups/src/v2/memory.rs +++ b/crates/libcgroups/src/v2/memory.rs @@ -14,6 +14,7 @@ const CGROUP_MEMORY_SWAP: &str = "memory.swap.max"; const CGROUP_MEMORY_MAX: &str = "memory.max"; const CGROUP_MEMORY_LOW: &str = "memory.low"; const MEMORY_STAT: &str = "memory.stat"; +const MEMORY_PSI: &str = "memory.pressure"; pub struct Memory {} @@ -37,6 +38,8 @@ impl StatsProvider for Memory { memswap: Self::get_memory_data(cgroup_path, "memory.swap", "fail")?, hierarchy: true, stats: stats::parse_flat_keyed_data(&cgroup_path.join(MEMORY_STAT))?, + psi: stats::psi_stats(&cgroup_path.join(MEMORY_PSI)) + .context("could not read memory psi")?, ..Default::default() }; diff --git a/crates/youki/Cargo.toml b/crates/youki/Cargo.toml index 54d5787c4..86bcd8233 100644 --- a/crates/youki/Cargo.toml +++ b/crates/youki/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tabwriter = "1" clap_complete = "3.2.5" +caps = "0.5.5" [dev-dependencies] serial_test = "0.9.0" diff --git a/crates/youki/src/commands/info.rs b/crates/youki/src/commands/info.rs index cb7a74031..f975d2953 100644 --- a/crates/youki/src/commands/info.rs +++ b/crates/youki/src/commands/info.rs @@ -21,6 +21,7 @@ pub fn info(_: Info) -> Result<()> { print_hardware(); print_cgroups(); print_namespaces(); + print_capabilities(); Ok(()) } @@ -223,6 +224,38 @@ pub fn print_namespaces() { } } +#[inline] +fn is_cap_available(caps: &caps::CapsHashSet, cap: caps::Capability) -> &'static str { + if caps.contains(&cap) { + "available" + } else { + "unavailable" + } +} + +pub fn print_capabilities() { + println!("Capabilities"); + if let Ok(current) = caps::read(None, caps::CapSet::Bounding) { + println!( + "{:<17} {}", + "CAP_BPF", + is_cap_available(¤t, caps::Capability::CAP_BPF) + ); + println!( + "{:<17} {}", + "CAP_PERFMON", + is_cap_available(¤t, caps::Capability::CAP_PERFMON) + ); + println!( + "{:<17} {}", + "CAP_CHECKPOINT_RESTORE", + is_cap_available(¤t, caps::Capability::CAP_CHECKPOINT_RESTORE) + ); + } else { + println!(""); + } +} + fn print_feature_status(config: &str, feature: &str, display: FeatureDisplay) { if let Some(status_flag) = find_parameter(config, feature) { let status = if status_flag == "y" {