use super::scx_defs::*;
pub(crate) fn decode_dsq_id(id: u64) -> String {
match id >> DSQ_TYPE_SHIFT {
DSQ_TYPE_LOCAL_ON => format!("SCX_DSQ_LOCAL_ON|{}", id & 0xffffffff),
DSQ_TYPE_BUILTIN => match (id & 0xffffffff) as u32 {
DSQ_INVALID => "SCX_DSQ_INVALID".into(),
DSQ_GLOBAL => "SCX_DSQ_GLOBAL".into(),
DSQ_LOCAL => "SCX_DSQ_LOCAL".into(),
DSQ_BYPASS => "SCX_DSQ_BYPASS".into(),
v => format!("BUILTIN({v})"),
},
_ => format!("DSQ(0x{id:x})"),
}
}
pub(crate) fn decode_cpumask(bits: u64) -> String {
decode_cpumask_multi(&[bits], None)
}
pub(crate) fn decode_cpumask_multi(words: &[u64], nr_cpus: Option<u32>) -> String {
let max_cpu = nr_cpus.unwrap_or(words.len() as u32 * 64);
let mut cpus = Vec::new();
for (word_idx, &bits) in words.iter().enumerate() {
let base = word_idx as u32 * 64;
if base >= max_cpu {
break;
}
let top = (max_cpu - base).min(64);
for i in 0..top {
if bits & (1u64 << i) != 0 {
cpus.push(base + i);
}
}
}
if cpus.is_empty() {
return "none".into();
}
let mut ranges = Vec::new();
let (mut s, mut e) = (cpus[0], cpus[0]);
for &c in &cpus[1..] {
if c == e + 1 {
e = c;
} else {
ranges.push(if s == e {
format!("{s}")
} else {
format!("{s}-{e}")
});
s = c;
e = c;
}
}
ranges.push(if s == e {
format!("{s}")
} else {
format!("{s}-{e}")
});
ranges.join(",")
}
pub(crate) fn decode_enq_flags(flags: u64) -> String {
decode_bitflags(flags, ENQ_FLAG_NAMES)
}
pub(crate) fn decode_exit_kind(kind: u64) -> String {
for &(val, name) in EXIT_KIND_NAMES {
if kind == val {
return name.into();
}
}
format!("UNKNOWN({kind})")
}
fn decode_bitflags(flags: u64, table: &[(u64, &str)]) -> String {
let mut parts = Vec::new();
for &(bit, name) in table {
if flags & bit != 0 {
parts.push(name);
}
}
if parts.is_empty() {
"NONE".into()
} else {
parts.join("|")
}
}
pub(crate) fn format_raw_arg(val: u64) -> String {
if val == 0 {
"int:0".into()
} else if val == 0xffffffffffffffff || val == 0xffffffff {
"int:-1".into()
} else if val == 1 {
"bool:true".into()
} else if (2..=0xff).contains(&val) {
format!("int:{val}")
} else if val > 0xff00000000000000 {
format!("ptr:{:04x}", val & 0xffff)
} else if val <= 0xffff && val.count_ones() >= 2 && val.count_ones() <= 16 {
format!("mask:0x{val:x}({})", decode_cpumask(val))
} else if val <= 0xffff {
format!("int:{val}")
} else if val >> DSQ_TYPE_SHIFT >= DSQ_TYPE_BUILTIN {
format!("dsq:{}", decode_dsq_id(val))
} else {
format!("hex:0x{val:x}")
}
}
pub(crate) fn decode_named_value(struct_name: &str, key: &str, val: &str) -> String {
decode_named_value_hinted(struct_name, key, val, None)
}
pub(crate) fn decode_named_value_hinted(
struct_name: &str,
key: &str,
val: &str,
hint: Option<super::btf::RenderHint>,
) -> String {
let as_u64 = || -> u64 {
if let Some(hex) = val.strip_prefix("0x") {
u64::from_str_radix(hex, 16).unwrap_or(0)
} else {
val.parse().unwrap_or(0)
}
};
match key {
"dsq_id" | "dsq" => decode_dsq_id(as_u64()),
"cpus_ptr" | "cpus" | "cpumask" | "cpumask_0" | "cpumask_1" | "cpumask_2" | "cpumask_3" => {
let v = as_u64();
format!("0x{v:x}({cpus})", cpus = decode_cpumask(v))
}
"enforce" => {
if val == "1" || val == "true" {
"true".into()
} else {
"false".into()
}
}
"enq_flags" | "enq" | "enqflags"
if struct_name.is_empty() || struct_name == "task_struct" =>
{
decode_enq_flags(as_u64())
}
"exit_kind" if struct_name.is_empty() || struct_name == "scx_exit_info" => {
decode_exit_kind(as_u64())
}
"sticky_cpu" | "sticky" => {
let v = as_u64();
if v == 0xffffffff || v == 0xffffffffffffffff {
"-1".into()
} else {
format!("{v}")
}
}
"cpu" | "rq_cpu" | "dst_cpu" | "dest_cpu" => val.to_string(),
"pid" => val.to_string(),
"task" => val.to_string(),
"slice" | "vtime" => {
let v = as_u64();
format!("{v}")
}
"weight" => val.to_string(),
"kick_flags" | "kick" => decode_kick_flags(as_u64()),
"ops_state" | "opss" => decode_ops_state(as_u64()),
"flags" | "scx_flags" if struct_name.is_empty() || struct_name == "task_struct" => {
let v = as_u64();
let mut parts = Vec::new();
if v & TASK_QUEUED != 0 {
parts.push("QUEUED");
}
if v & TASK_RESET_RUNNABLE_AT != 0 {
parts.push("RESET_RUNNABLE_AT");
}
if v & TASK_DEQD_FOR_SLEEP != 0 {
parts.push("DEQD_FOR_SLEEP");
}
let state = (v >> TASK_STATE_SHIFT) & TASK_STATE_MASK;
match state {
TASK_STATE_INIT => parts.push("INIT"),
TASK_STATE_READY => parts.push("READY"),
TASK_STATE_ENABLED => parts.push("ENABLED"),
_ => {}
}
if parts.is_empty() {
"NONE".into()
} else {
parts.join("|")
}
}
_ if key.contains("cpumask") || key.contains("cpus") => {
let v = as_u64();
format!("0x{v:x}({cpus})", cpus = decode_cpumask(v))
}
_ => {
let v = as_u64();
if val.starts_with("0x") || val.parse::<u64>().is_ok() {
match hint {
Some(super::btf::RenderHint::Decimal) => format!("{v}"),
Some(super::btf::RenderHint::Signed) => {
format!("{}", v as i64)
}
Some(super::btf::RenderHint::Bool) => {
if v != 0 {
"true".into()
} else {
"false".into()
}
}
Some(super::btf::RenderHint::Hex) | None => format!("0x{v:x}"),
}
} else {
val.to_string()
}
}
}
}
pub(crate) fn decode_kick_flags(flags: u64) -> String {
decode_bitflags(flags, KICK_FLAG_NAMES)
}
pub(crate) fn decode_ops_state(state: u64) -> String {
use super::scx_defs::*;
match state & 0xff {
OPS_NONE => "NONE".into(),
OPS_QUEUEING => "QUEUEING".into(),
OPS_QUEUED => "QUEUED".into(),
OPS_DISPATCHING => "DISPATCHING".into(),
v => format!("OPSS({v})"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_dsq_id_zero() {
assert_eq!(decode_dsq_id(0), "DSQ(0x0)");
}
#[test]
fn decode_dsq_id_global() {
assert_eq!(decode_dsq_id((1u64 << 63) | 1), "SCX_DSQ_GLOBAL");
}
#[test]
fn decode_dsq_id_local() {
assert_eq!(decode_dsq_id((1u64 << 63) | 2), "SCX_DSQ_LOCAL");
}
#[test]
fn decode_dsq_id_bypass() {
assert_eq!(decode_dsq_id((1u64 << 63) | 3), "SCX_DSQ_BYPASS");
}
#[test]
fn decode_dsq_id_invalid() {
assert_eq!(decode_dsq_id(1u64 << 63), "SCX_DSQ_INVALID");
}
#[test]
fn decode_dsq_id_local_on() {
let id = (1u64 << 63) | (1u64 << 62) | 7;
assert_eq!(decode_dsq_id(id), "SCX_DSQ_LOCAL_ON|7");
}
#[test]
fn decode_dsq_id_user_dsq() {
assert_eq!(decode_dsq_id(42), "DSQ(0x2a)");
}
#[test]
fn decode_cpumask_none() {
assert_eq!(decode_cpumask(0), "none");
}
#[test]
fn decode_cpumask_single() {
assert_eq!(decode_cpumask(1), "0");
}
#[test]
fn decode_cpumask_contiguous() {
assert_eq!(decode_cpumask(0xf), "0-3");
}
#[test]
fn decode_cpumask_gaps() {
assert_eq!(decode_cpumask(0x33), "0-1,4-5");
}
#[test]
fn decode_cpumask_scattered() {
assert_eq!(decode_cpumask(0x15), "0,2,4");
}
#[test]
fn decode_enq_flags_none() {
assert_eq!(decode_enq_flags(0), "NONE");
}
#[test]
fn decode_enq_flags_wakeup() {
assert_eq!(decode_enq_flags(ENQ_WAKEUP), "WAKEUP");
}
#[test]
fn decode_enq_flags_multi() {
assert_eq!(decode_enq_flags(ENQ_WAKEUP | ENQ_HEAD), "WAKEUP|HEAD");
}
#[test]
fn decode_enq_flags_preempt() {
assert_eq!(decode_enq_flags(ENQ_PREEMPT), "PREEMPT");
}
#[test]
fn decode_enq_flags_reenq() {
assert_eq!(decode_enq_flags(ENQ_REENQ), "REENQ");
}
#[test]
fn decode_exit_kind_all() {
assert_eq!(decode_exit_kind(EXIT_NONE), "NONE");
assert_eq!(decode_exit_kind(EXIT_DONE), "DONE");
assert_eq!(decode_exit_kind(EXIT_UNREG), "UNREG");
assert_eq!(decode_exit_kind(EXIT_UNREG_BPF), "UNREG_BPF");
assert_eq!(decode_exit_kind(EXIT_UNREG_KERN), "UNREG_KERN");
assert_eq!(decode_exit_kind(EXIT_SYSRQ), "SYSRQ");
assert_eq!(decode_exit_kind(EXIT_ERROR), "ERROR");
assert_eq!(decode_exit_kind(EXIT_ERROR_BPF), "ERROR_BPF");
assert_eq!(decode_exit_kind(EXIT_ERROR_STALL), "ERROR_STALL");
assert_eq!(decode_exit_kind(9999), "UNKNOWN(9999)");
}
#[test]
fn format_raw_arg_zero() {
assert_eq!(format_raw_arg(0), "int:0");
}
#[test]
fn format_raw_arg_one() {
assert_eq!(format_raw_arg(1), "bool:true");
}
#[test]
fn format_raw_arg_minus_one() {
assert_eq!(format_raw_arg(0xffffffffffffffff), "int:-1");
}
#[test]
fn format_raw_arg_minus_one_32() {
assert_eq!(format_raw_arg(0xffffffff), "int:-1");
}
#[test]
fn format_raw_arg_small_int() {
assert_eq!(format_raw_arg(42), "int:42");
}
#[test]
fn format_raw_arg_cpumask() {
let v = 0x303u64;
let out = format_raw_arg(v);
assert!(out.starts_with("mask:"), "got: {out}");
assert!(out.contains("0-1,8-9"));
}
#[test]
fn format_raw_arg_kernel_ptr() {
let out = format_raw_arg(0xffff888100123456);
assert!(out.starts_with("ptr:"), "got: {out}");
}
#[test]
fn format_raw_arg_dsq_id() {
let v = (1u64 << 63) | 1;
let out = format_raw_arg(v);
assert!(out.contains("SCX_DSQ_GLOBAL"), "got: {out}");
}
#[test]
fn decode_named_value_dsq_id() {
let v = (1u64 << 63) | 2;
assert_eq!(
decode_named_value("", "dsq_id", &v.to_string()),
"SCX_DSQ_LOCAL"
);
}
#[test]
fn decode_named_value_cpus_ptr() {
let out = decode_named_value("", "cpus_ptr", "15");
assert!(out.contains("0-3"), "got: {out}");
}
#[test]
fn decode_named_value_enq_flags() {
assert_eq!(
decode_named_value("task_struct", "enq_flags", "1"),
"WAKEUP"
);
}
#[test]
fn decode_named_value_exit_kind() {
assert_eq!(
decode_named_value("scx_exit_info", "exit_kind", "1024"),
"ERROR"
);
}
#[test]
fn decode_named_value_sticky_minus_one() {
assert_eq!(
decode_named_value("", "sticky_cpu", &0xffffffffu64.to_string()),
"-1"
);
}
#[test]
fn decode_named_value_pid_passthrough() {
assert_eq!(decode_named_value("", "pid", "1234"), "1234");
}
#[test]
fn decode_named_value_unknown_key() {
assert_eq!(decode_named_value("", "foobar", "hello"), "hello");
}
#[test]
fn decode_named_value_enforce_true() {
assert_eq!(decode_named_value("", "enforce", "1"), "true");
}
#[test]
fn decode_named_value_scx_flags() {
let v = TASK_QUEUED | (TASK_STATE_ENABLED << TASK_STATE_SHIFT);
assert_eq!(
decode_named_value("task_struct", "scx_flags", &v.to_string()),
"QUEUED|ENABLED"
);
}
#[test]
fn enq_flag_names_no_overlap() {
for (i, &(a_val, a_name)) in ENQ_FLAG_NAMES.iter().enumerate() {
for &(b_val, b_name) in &ENQ_FLAG_NAMES[i + 1..] {
assert_eq!(
a_val & b_val,
0,
"flag overlap: {a_name} (0x{a_val:x}) & {b_name} (0x{b_val:x})",
);
}
}
}
#[test]
fn exit_kind_names_no_duplicate_values() {
for (i, &(a_val, a_name)) in EXIT_KIND_NAMES.iter().enumerate() {
for &(b_val, b_name) in &EXIT_KIND_NAMES[i + 1..] {
assert_ne!(
a_val, b_val,
"duplicate exit kind value: {a_name} and {b_name} both = {a_val}",
);
}
}
}
#[test]
fn dsq_type_shift_and_builtin_values() {
assert_eq!(DSQ_TYPE_SHIFT, 62);
assert_eq!(DSQ_TYPE_BUILTIN << DSQ_TYPE_SHIFT, 1u64 << 63);
assert_eq!(
DSQ_TYPE_LOCAL_ON << DSQ_TYPE_SHIFT,
(1u64 << 63) | (1u64 << 62)
);
assert_eq!(DSQ_INVALID, 0);
assert_eq!(DSQ_GLOBAL, 1);
assert_eq!(DSQ_LOCAL, 2);
assert_eq!(DSQ_BYPASS, 3);
}
#[test]
fn task_state_constants_sequential() {
assert_eq!(TASK_STATE_INIT, 1);
assert_eq!(TASK_STATE_READY, 2);
assert_eq!(TASK_STATE_ENABLED, 3);
assert_eq!(TASK_STATE_MASK, 3);
}
#[test]
fn decode_enq_flags_all_set() {
let all = ENQ_WAKEUP
| ENQ_HEAD
| ENQ_PREEMPT
| ENQ_REENQ
| ENQ_LAST
| ENQ_CLEAR_OPSS
| ENQ_DSQ_PRIQ
| ENQ_NESTED;
let out = decode_enq_flags(all);
for &(_, name) in ENQ_FLAG_NAMES {
assert!(out.contains(name), "missing flag {name} in '{out}'");
}
}
#[test]
fn decode_exit_kind_between_known() {
assert_eq!(decode_exit_kind(2), "UNKNOWN(2)");
assert_eq!(decode_exit_kind(100), "UNKNOWN(100)");
}
#[test]
fn format_raw_arg_boundary_0x100() {
let out = format_raw_arg(0x100);
assert_eq!(out, "int:256");
}
#[test]
fn format_raw_arg_boundary_0x10000() {
let out = format_raw_arg(0x10000);
assert_eq!(out, "hex:0x10000");
}
#[test]
fn format_raw_arg_boundary_0xff() {
assert_eq!(format_raw_arg(0xff), "int:255");
}
#[test]
fn decode_cpumask_bit_63() {
assert_eq!(decode_cpumask(1u64 << 63), "63");
}
#[test]
fn decode_cpumask_all_bits() {
assert_eq!(decode_cpumask(u64::MAX), "0-63");
}
#[test]
fn decode_dsq_id_builtin_unknown() {
let id = (DSQ_TYPE_BUILTIN << DSQ_TYPE_SHIFT) | 99;
assert_eq!(decode_dsq_id(id), "BUILTIN(99)");
}
#[test]
fn decode_named_value_enforce_true_literal() {
assert_eq!(decode_named_value("", "enforce", "true"), "true");
}
#[test]
fn decode_named_value_enforce_zero() {
assert_eq!(decode_named_value("", "enforce", "0"), "false");
}
#[test]
fn decode_named_value_enforce_other() {
assert_eq!(decode_named_value("", "enforce", "2"), "false");
}
#[test]
fn decode_named_value_dsq_id_hex_prefix() {
let hex_val = format!("0x{:x}", (DSQ_TYPE_BUILTIN << DSQ_TYPE_SHIFT) | 1);
assert_eq!(decode_named_value("", "dsq_id", &hex_val), "SCX_DSQ_GLOBAL");
}
#[test]
fn decode_named_value_slice_key() {
assert_eq!(decode_named_value("", "slice", "5000000"), "5000000");
}
#[test]
fn decode_named_value_vtime_key() {
assert_eq!(decode_named_value("", "vtime", "123456789"), "123456789");
}
#[test]
fn decode_named_value_slice_hex_prefix() {
assert_eq!(decode_named_value("", "slice", "0x4c4b40"), "5000000");
}
#[test]
fn decode_named_value_sticky_cpu_64bit_minus_one() {
assert_eq!(
decode_named_value("", "sticky_cpu", &0xffffffffffffffffu64.to_string()),
"-1"
);
}
#[test]
fn decode_named_value_sticky_cpu_normal() {
assert_eq!(decode_named_value("", "sticky_cpu", "7"), "7");
}
#[test]
fn format_raw_arg_two() {
assert_eq!(format_raw_arg(2), "int:2");
}
#[test]
fn decode_cpumask_multi_empty() {
assert_eq!(decode_cpumask_multi(&[0, 0, 0, 0], None), "none");
}
#[test]
fn decode_cpumask_multi_word0_only() {
assert_eq!(decode_cpumask_multi(&[0xf, 0, 0, 0], None), "0-3");
}
#[test]
fn decode_cpumask_multi_word1() {
assert_eq!(decode_cpumask_multi(&[0, 1, 0, 0], None), "64");
}
#[test]
fn decode_cpumask_multi_span_words() {
assert_eq!(decode_cpumask_multi(&[1u64 << 63, 1, 0, 0], None), "63-64");
}
#[test]
fn decode_cpumask_multi_all_four_words() {
assert_eq!(decode_cpumask_multi(&[1, 1, 1, 1], None), "0,64,128,192");
}
#[test]
fn decode_cpumask_multi_contiguous_across_boundary() {
let w0 = (1u64 << 62) | (1u64 << 63);
let w1 = 0b11u64;
assert_eq!(decode_cpumask_multi(&[w0, w1, 0, 0], None), "62-65");
}
#[test]
fn decode_cpumask_multi_single_word_compat() {
assert_eq!(decode_cpumask_multi(&[0x33], None), decode_cpumask(0x33));
}
#[test]
fn decode_cpumask_multi_word3_high() {
assert_eq!(decode_cpumask_multi(&[0, 0, 0, 1u64 << 63], None), "255");
}
#[test]
fn decode_cpumask_multi_nr_cpus_truncates() {
let w0 = 0xff;
let w1 = 0xffffffffffffffff; assert_eq!(decode_cpumask_multi(&[w0, w1, 0, 0], Some(8)), "0-7",);
}
#[test]
fn decode_cpumask_multi_nr_cpus_mid_word() {
let w0 = 0x3ff; assert_eq!(decode_cpumask_multi(&[w0], Some(10)), "0-9");
let w0_garbage = 0xffff_ffff_ffff_ffff;
assert_eq!(decode_cpumask_multi(&[w0_garbage], Some(10)), "0-9");
}
#[test]
fn decode_cpumask_multi_nr_cpus_word_boundary() {
let w0 = 0xff;
let w1 = 0xdeadbeef;
assert_eq!(decode_cpumask_multi(&[w0, w1], Some(64)), "0-7",);
}
#[test]
fn decode_cpumask_multi_nr_cpus_zero() {
assert_eq!(decode_cpumask_multi(&[0xff], Some(0)), "none");
}
#[test]
fn decode_kick_flags_idle() {
assert_eq!(decode_kick_flags(1), "IDLE");
}
#[test]
fn decode_kick_flags_preempt() {
assert_eq!(decode_kick_flags(2), "PREEMPT");
}
#[test]
fn decode_kick_flags_wait() {
assert_eq!(decode_kick_flags(4), "WAIT");
}
#[test]
fn decode_kick_flags_combo() {
assert_eq!(decode_kick_flags(1 | 2), "IDLE|PREEMPT");
}
#[test]
fn decode_kick_flags_none() {
assert_eq!(decode_kick_flags(0), "NONE");
}
#[test]
fn decode_ops_state_none() {
assert_eq!(decode_ops_state(0), "NONE");
}
#[test]
fn decode_ops_state_queueing() {
assert_eq!(decode_ops_state(1), "QUEUEING");
}
#[test]
fn decode_ops_state_queued() {
assert_eq!(decode_ops_state(2), "QUEUED");
}
#[test]
fn decode_ops_state_dispatching() {
assert_eq!(decode_ops_state(3), "DISPATCHING");
}
#[test]
fn decode_ops_state_unknown() {
assert_eq!(decode_ops_state(99), "OPSS(99)");
}
#[test]
fn decode_named_value_cpumask_0() {
let out = decode_named_value("", "cpumask_0", "15");
assert!(out.contains("0-3"), "got: {out}");
}
#[test]
fn decode_named_value_cpumask_1() {
let out = decode_named_value("", "cpumask_1", "1");
assert!(out.contains("0x1"), "got: {out}");
}
#[test]
fn decode_named_value_cpumask_3() {
let out = decode_named_value("", "cpumask_3", "0");
assert!(out.contains("0x0"), "got: {out}");
}
#[test]
fn decode_named_value_key_containing_cpumask() {
let out = decode_named_value("", "my_cpumask_field", "255");
assert!(out.contains("0-7"), "wildcard cpumask key: {out}");
}
#[test]
fn decode_named_value_flags_wrong_struct_passthrough() {
let out = decode_named_value("rq_flags", "flags", "42");
assert_ne!(out, "QUEUED", "should not apply scx decoder to rq_flags");
assert_eq!(out, "0x2a");
}
#[test]
fn decode_named_value_flags_task_struct_decodes() {
let v = TASK_QUEUED;
assert_eq!(
decode_named_value("task_struct", "flags", &v.to_string()),
"QUEUED"
);
}
#[test]
fn decode_named_value_enq_flags_wrong_struct_passthrough() {
let out = decode_named_value("some_other", "enq_flags", "1");
assert_ne!(out, "WAKEUP", "should not apply scx decoder to some_other");
assert_eq!(out, "0x1");
}
#[test]
fn decode_named_value_exit_kind_wrong_struct_passthrough() {
let out = decode_named_value("other_struct", "exit_kind", "1024");
assert_ne!(out, "ERROR", "should not apply scx decoder to other_struct");
assert_eq!(out, "0x400");
}
#[test]
fn decode_named_value_enq_flags_empty_struct_decodes() {
assert_eq!(decode_named_value("", "enq_flags", "1"), "WAKEUP");
}
#[test]
fn decode_named_value_exit_kind_empty_struct_decodes() {
assert_eq!(decode_named_value("", "exit_kind", "1024"), "ERROR");
}
}