scx_stats: Implement #stat_doc to autogen doc from stat desc

The doc of scx_layered `Opt` is out of sync.

Implement attribute macro #stat_doc to generate doc from the `desc`
property.

Apply #stat_doc to `LayerStats` and `SysStats in scx_layered.

Signed-off-by : Ming Yang <minos.future@gmail.com>
This commit is contained in:
Ming Yang 2024-09-27 22:46:12 -07:00
parent e8ebc09ced
commit 28bfd2986a
6 changed files with 121 additions and 48 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ libbpf/
*.gitignore
PKGBUILD
target
*.swp
.cache/

1
Cargo.lock generated
View File

@ -1827,6 +1827,7 @@ dependencies = [
"crossbeam",
"libc",
"log",
"proc-macro2",
"quote",
"scx_stats_derive",
"serde",

View File

@ -12,6 +12,7 @@ anyhow = "1.0.65"
crossbeam = "0.8.4"
libc = "0.2.137"
log = "0.4.17"
proc-macro2 = "1.0"
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -3,6 +3,7 @@ use scx_stats::{StatsData, StatsKind, StatsMetaAux};
use std::sync::atomic::{AtomicU64, Ordering};
use syn::parse_macro_input;
use syn::spanned::Spanned;
use syn::{Attribute, Data, DeriveInput, Fields, Lit};
static ASSERT_IDX: AtomicU64 = AtomicU64::new(0);
@ -46,3 +47,99 @@ pub fn stat(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
output.into()
}
#[proc_macro_attribute]
pub fn stat_doc(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let ident = input.ident;
let vis = input.vis;
let attrs = input.attrs;
let generics = input.generics;
let data = input.data;
let mut output = proc_macro2::TokenStream::new();
if let Data::Struct(data_struct) = data {
let fields = match data_struct.fields {
Fields::Named(fields_named) => fields_named.named,
_ => {
return syn::Error::new_spanned(
ident,
"stat attribute can only be used on structs with named fields",
)
.to_compile_error()
.into();
}
};
let mut new_fields = Vec::new();
for mut field in fields {
let mut doc_string = None;
let mut new_attrs = Vec::new();
for attr in field.attrs.clone() {
if attr.path().is_ident("stat") {
// Parse the arguments within #[stat(...)]
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("desc") {
// Extract the literal string value from `desc`
let desc_literal: Lit = meta.value()?.parse()?;
if let Lit::Str(lit_str) = desc_literal {
doc_string = Some(lit_str.value());
}
}
Ok(())
})
.unwrap_or_else(|err| {
panic!("Failed to parse the stat attribute: {}", err);
});
}
new_attrs.push(attr);
}
// If a description string was found, add a #[doc = "..."] attribute
if let Some(description) = doc_string {
let doc_attr = Attribute {
pound_token: syn::token::Pound::default(),
style: syn::AttrStyle::Outer,
bracket_token: syn::token::Bracket::default(),
meta: syn::Meta::NameValue(syn::MetaNameValue {
path: syn::Path::from(format_ident!("doc")),
eq_token: syn::token::Eq::default(),
value: syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(syn::LitStr::new(&description, field.span())),
attrs: vec![],
}),
}),
};
new_attrs.push(doc_attr);
}
field.attrs = new_attrs;
new_fields.push(field);
}
// Rebuild the struct with the modified fields
let struct_def = quote! {
#(#attrs)*
#vis struct #ident #generics {
#(#new_fields),*
}
};
output.extend(struct_def);
return output.into();
}
// If not a struct with named fields, return an error
syn::Error::new_spanned(
ident,
"stat attribute can only be used on structs with named fields",
)
.to_compile_error()
.into()
}

View File

@ -331,6 +331,7 @@ lazy_static::lazy_static! {
/// also an scx_stat server listening on /var/run/scx/root/stat that can
/// be monitored by running `scx_layered --monitor INTERVAL` separately.
///
/// ```bash
/// $ scx_layered --monitor 1
/// tot= 117909 local=86.20 open_idle= 0.21 affn_viol= 1.37 proc=6ms
/// busy= 34.2 util= 1733.6 load= 21744.1 fallback_cpu= 1
@ -343,45 +344,11 @@ lazy_static::lazy_static! {
/// normal : util/frac= 502.9/ 29.0 load/frac= 314.5: 1.4 tasks= 3512
/// tot= 45434 local=80.97 open_idle= 0.16 preempt= 0.00 affn_viol= 3.56
/// cpus= 50 [ 50, 50] fbfffffe 000fffff
/// ```
///
/// Global statistics:
/// Global statistics: see [`SysStats`]
///
/// - tot: Total scheduling events in the period.
///
/// - local: % that got scheduled directly into an idle CPU.
///
/// - open_idle: % of open layer tasks scheduled into occupied idle CPUs.
///
/// - affn_viol: % which violated configured policies due to CPU affinity
/// restrictions.
///
/// - proc: CPU time this binary consumed during the period.
///
/// - busy: CPU busy % (100% means all CPUs were fully occupied)
///
/// - util: CPU utilization % (100% means one CPU was fully occupied)
///
/// - load: Sum of weight * duty_cycle for all tasks
///
/// Per-layer statistics:
///
/// - util/frac: CPU utilization and fraction % (sum of fractions across
/// layers is always 100%).
///
/// - load/frac: Load sum and fraction %.
///
/// - tasks: Number of tasks.
///
/// - tot: Total scheduling events.
///
/// - open_idle: % of tasks scheduled into idle CPUs occupied by other layers.
///
/// - preempt: % of tasks that preempted other tasks.
///
/// - affn_viol: % which violated configured policies due to CPU affinity
/// restrictions.
///
/// - cpus: CUR_NR_CPUS [MIN_NR_CPUS, MAX_NR_CPUS] CUR_CPU_MASK
/// Per-layer statistics: see [`LayerStats`]
///
#[derive(Debug, Parser)]
#[command(verbatim_doc_comment)]

View File

@ -16,6 +16,7 @@ use chrono::DateTime;
use chrono::Local;
use log::warn;
use scx_stats::prelude::*;
use scx_stats_derive::stat_doc;
use scx_stats_derive::Stats;
use serde::Deserialize;
use serde::Serialize;
@ -44,6 +45,7 @@ fn fmt_num(v: u64) -> String {
}
}
#[stat_doc]
#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
#[stat(_om_prefix = "l_", _om_label = "layer_name")]
pub struct LayerStats {
@ -55,9 +57,9 @@ pub struct LayerStats {
pub load: f64,
#[stat(desc = "fraction of total load")]
pub load_frac: f64,
#[stat(desc = "# tasks")]
#[stat(desc = "count of tasks")]
pub tasks: u32,
#[stat(desc = "# sched events duringg the period")]
#[stat(desc = "count of sched events during the period")]
pub total: u64,
#[stat(desc = "% dispatched into idle CPU")]
pub sel_local: f64,
@ -67,7 +69,7 @@ pub struct LayerStats {
pub enq_expire: f64,
#[stat(desc = "% re-enqueued due to RT preemption")]
pub enq_reenq: f64,
#[stat(desc = "# times exec duration < min_exec_us")]
#[stat(desc = "count of times exec duration < min_exec_us")]
pub min_exec: f64,
#[stat(desc = "total exec durations extended due to min_exec_us")]
pub min_exec_us: u64,
@ -95,7 +97,7 @@ pub struct LayerStats {
pub keep_fail_busy: f64,
#[stat(desc = "whether is exclusive", _om_skip)]
pub is_excl: u32,
#[stat(desc = "# times an excl task skipped a CPU as the sibling was also excl")]
#[stat(desc = "count of times an excl task skipped a CPU as the sibling was also excl")]
pub excl_collision: f64,
#[stat(desc = "% a sibling CPU was preempted for an exclusive task")]
pub excl_preempt: f64,
@ -103,7 +105,7 @@ pub struct LayerStats {
pub kick: f64,
#[stat(desc = "% yielded")]
pub yielded: f64,
#[stat(desc = "# times yield was ignored")]
#[stat(desc = "count of times yield was ignored")]
pub yield_ignore: u64,
#[stat(desc = "% migrated across CPUs")]
pub migration: f64,
@ -117,7 +119,7 @@ pub struct LayerStats {
pub xlayer_rewake: f64,
#[stat(desc = "mask of allocated CPUs", _om_skip)]
pub cpus: Vec<u32>,
#[stat(desc = "# of CPUs assigned")]
#[stat(desc = "count of of CPUs assigned")]
pub cur_nr_cpus: u32,
#[stat(desc = "minimum # of CPUs assigned")]
pub min_nr_cpus: u32,
@ -326,12 +328,13 @@ impl LayerStats {
}
}
#[stat_doc]
#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
#[stat(top)]
pub struct SysStats {
#[stat(desc = "timestamp", _om_skip)]
pub at: f64,
#[stat(desc = "# sched events during the period")]
#[stat(desc = "count of sched events during the period")]
pub total: u64,
#[stat(desc = "% dispatched directly into an idle CPU")]
pub local: f64,
@ -339,13 +342,15 @@ pub struct SysStats {
pub open_idle: f64,
#[stat(desc = "% violated config due to CPU affinity")]
pub affn_viol: f64,
#[stat(desc = "# times an excl task skipped a CPU as the sibling was also excl")]
#[stat(desc = "count of times an excl task skipped a CPU as the sibling was also excl")]
pub excl_collision: f64,
#[stat(desc = "# times a sibling CPU was preempted for an excl task")]
#[stat(desc = "count of times a sibling CPU was preempted for an excl task")]
pub excl_preempt: f64,
#[stat(desc = "# times a CPU skipped dispatching due to an excl task on the sibling")]
#[stat(desc = "count of times a CPU skipped dispatching due to an excl task on the sibling")]
pub excl_idle: f64,
#[stat(desc = "# times an idle sibling CPU was woken up after an excl task is finished")]
#[stat(
desc = "count of times an idle sibling CPU was woken up after an excl task is finished"
)]
pub excl_wakeup: f64,
#[stat(desc = "CPU time this binary consumed during the period")]
pub proc_ms: u64,