1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-06-10 08:46:21 +02:00

Retrieve cpu stats from cgroups v1

This commit is contained in:
Furisto 2021-07-25 19:54:42 +02:00
parent 201274b12b
commit 87c7db7e5a
2 changed files with 157 additions and 4 deletions

View File

@ -1,9 +1,12 @@
use std::path::Path;
use anyhow::Result;
use anyhow::{bail, Context, Result};
use oci_spec::{LinuxCpu, LinuxResources};
use crate::cgroups::common;
use crate::cgroups::{
common,
stats::{CpuThrottling, StatsProvider},
};
use super::Controller;
@ -12,6 +15,7 @@ const CGROUP_CPU_QUOTA: &str = "cpu.cfs_quota_us";
const CGROUP_CPU_PERIOD: &str = "cpu.cfs_period_us";
const CGROUP_CPU_RT_RUNTIME: &str = "cpu.rt_runtime_us";
const CGROUP_CPU_RT_PERIOD: &str = "cpu.rt_period_us";
const CGROUP_CPU_STAT: &str = "cpu.stat";
pub struct Cpu {}
@ -44,6 +48,51 @@ impl Controller for Cpu {
}
}
impl StatsProvider for Cpu {
type Stats = CpuThrottling;
fn stats(cgroup_path: &Path) -> Result<Self::Stats> {
let mut stats = CpuThrottling::default();
let stat_path = cgroup_path.join(CGROUP_CPU_STAT);
let stat_content = common::read_cgroup_file(&stat_path)?;
let parts: Vec<&str> = stat_content.split_ascii_whitespace().collect();
if parts.len() < 6 {
bail!(
"{} contains less than the expected number of entries",
stat_path.display()
);
}
if parts[0] != "nr_periods" {
bail!(
"{} does not contain the number of elapsed periods",
stat_path.display()
);
}
if parts[2] != "nr_throttled" {
bail!(
"{} does not contain the number of throttled periods",
stat_path.display()
);
}
if parts[4] != "throttled_time" {
bail!(
"{} does not contain the total time tasks have spent throttled",
stat_path.display()
);
}
stats.periods = parts[1].parse().context("failed to parse nr_periods")?;
stats.throttled_periods = parts[3].parse().context("failed to parse nr_throttled")?;
stats.throttled_time = parts[5].parse().context("failed to parse throttled time")?;
Ok(stats)
}
}
impl Cpu {
fn apply(root_path: &Path, cpu: &LinuxCpu) -> Result<()> {
if let Some(cpu_shares) = cpu.shares {

View File

@ -1,10 +1,24 @@
use std::path::Path;
use anyhow::Result;
use anyhow::{bail, Context, Result};
use oci_spec::LinuxResources;
use crate::cgroups::{
common,
stats::{CpuUsage, StatsProvider},
};
use super::Controller;
// Contains user mode and kernel mode cpu consumption
const CGROUP_CPUACCT_STAT: &str = "cpuacct.stat";
// Contains overall cpu consumption
const CGROUP_CPUACCT_USAGE: &str = "cpuacct.usage";
// Contains user mode and kernel mode cpu consumption differentiated by core
const CGROUP_CPUACCT_USAGE_ALL: &str = "cpuacct.usage_all";
// Contains overall cpu consumption differentiated by core
const CGROUP_CPUACCT_PERCPU: &str = "cpuacct.usage_percpu";
pub struct CpuAcct {}
impl Controller for CpuAcct {
@ -14,12 +28,102 @@ impl Controller for CpuAcct {
Ok(())
}
// apply never needs to be called, for accounting only
fn needs_to_handle(_linux_resources: &LinuxResources) -> Option<&Self::Resource> {
None
}
}
impl StatsProvider for CpuAcct {
type Stats = CpuUsage;
fn stats(cgroup_path: &Path) -> Result<Self::Stats> {
let mut stats = CpuUsage::default();
Self::get_total_cpu_usage(cgroup_path, &mut stats)?;
Self::get_per_core_usage(cgroup_path, &mut stats)?;
Ok(stats)
}
}
impl CpuAcct {
fn get_total_cpu_usage(cgroup_path: &Path, stats: &mut CpuUsage) -> Result<()> {
let stat_file_path = cgroup_path.join(CGROUP_CPUACCT_STAT);
let stat_file_content = common::read_cgroup_file(&stat_file_path)?;
// the first two entries of the file should look like this
// user 746908
// system 213896
let parts: Vec<&str> = stat_file_content.split_whitespace().collect();
if parts.len() < 4 {
bail!(
"{} contains less than the expected number of entries",
stat_file_path.display()
);
}
if !parts[0].eq("user") {
bail!(
"{} does not contain user mode cpu usage",
stat_file_path.display()
);
}
if !parts[2].eq("system") {
bail!(
"{} does not contain kernel mode cpu usage",
stat_file_path.display()
);
}
stats.usage_user = parts[1]
.parse()
.context("failed to parse user mode cpu usage")?;
stats.usage_kernel = parts[3]
.parse()
.context("failed to parse kernel mode cpu usage")?;
let total = common::read_cgroup_file(cgroup_path.join(CGROUP_CPUACCT_USAGE))?;
stats.usage_total = total
.trim()
.parse()
.context("failed to parse total cpu usage")?;
Ok(())
}
fn get_per_core_usage(cgroup_path: &Path, stats: &mut CpuUsage) -> Result<()> {
let all_content = common::read_cgroup_file(cgroup_path.join(CGROUP_CPUACCT_USAGE_ALL))?;
// first line is header, skip it
for entry in all_content.lines().skip(1) {
let entry_parts: Vec<&str> = entry.split_ascii_whitespace().collect();
if entry_parts.len() != 3 {
continue;
}
stats.per_core_usage_user.push(
entry_parts[1]
.parse()
.context("failed to parse per core user mode cpu usage")?,
);
stats.per_core_usage_kernel.push(
entry_parts[2]
.parse()
.context("failed to parse per core kernel mode cpu usage")?,
);
}
let percpu_content = common::read_cgroup_file(cgroup_path.join(CGROUP_CPUACCT_PERCPU))?;
stats.per_core_usage_total = percpu_content
.split_ascii_whitespace()
.map(|v| v.parse())
.collect::<Result<Vec<_>, _>>()
.context("failed to parse per core cpu usage")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::fs;