Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/analytics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use rule_templates::{
time_series_stat_intra_run_comparison_rule, time_series_stat_run_comparison_rule,
time_series_stat_threshold_rule,
};
use rules::multi_data_rules::get_multi_data_rules;
use rules::multi_data_rules::{get_multi_data_rules, PreemptLazyDetectedRule};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Mutex};

Expand Down Expand Up @@ -79,11 +79,12 @@ impl<'a> AnalyticalEngine<'a> {
}

for multi_data_rule in &self.multi_data_rules {
multi_data_rule.analyze(
&mut self.findings,
&self.all_processed_data,
processed_data_accessor,
);
let immutable_data: HashMap<String, &ProcessedData> = self
.all_processed_data
.iter()
.map(|(k, v)| (k.clone(), &**v))
.collect();
multi_data_rule.analyze(&mut self.findings, &immutable_data, processed_data_accessor);
}
}

Expand Down Expand Up @@ -205,8 +206,8 @@ pub trait Analyze {
pub trait MultiDataAnalyze {
fn analyze(
&self,
findings: &HashMap<String, DataFindings>,
all_processed_data: &HashMap<String, &mut ProcessedData>,
findings: &mut HashMap<String, DataFindings>,
all_processed_data: &HashMap<String, &ProcessedData>,
processed_data_accessor: &mut ProcessedDataAccessor,
);
}
Expand Down Expand Up @@ -253,20 +254,19 @@ macro_rules! multi_data_analytical_rules {
}

impl MultiDataAnalyticalRule {
pub fn analyze(&self, _findings: &mut HashMap<String, DataFindings>, _all_processed_data: &HashMap<String, &mut ProcessedData>, _processed_data_accessor: &mut ProcessedDataAccessor) {
pub fn analyze(&self, findings: &mut HashMap<String, DataFindings>, all_processed_data: &HashMap<String, &ProcessedData>, processed_data_accessor: &mut ProcessedDataAccessor) {
match self {
$(
MultiDataAnalyticalRule::$multi_data_analytical_rule(ref multi_data_analytical_rule) => multi_data_analytical_rule.analyze(findings, all_processed_data, processed_data_accessor),
)*
_ => todo!(),
}
}
}
};
}

// Register all multi-data-type rule templates here
multi_data_analytical_rules!();
multi_data_analytical_rules!(PreemptLazyDetectedRule);

#[cfg(test)]
mod tests {
Expand Down
137 changes: 135 additions & 2 deletions src/analytics/rules/multi_data_rules.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,138 @@
use crate::analytics::MultiDataAnalyticalRule;
use crate::analytics::{
AnalyticalFinding, DataFindings, MultiDataAnalyticalRule, MultiDataAnalyze, Score,
};
use crate::data::common::data_formats::ProcessedData;
use crate::data::common::processed_data_accessor::ProcessedDataAccessor;
use std::collections::HashMap;

const SYSTEMINFO: &str = "systeminfo";
const KERNEL_CONFIG: &str = "kernel_config";

// TODO: implement key-value data
const PREEMPT_LAZY_MESSAGE: &str = "Linux 7.0 changed the default preemption model to PREEMPT_LAZY. If you observe unexpected performance differences, consider setting the preemption model to PREEMPT_NONE via kernel boot parameter (preempt=none) or kernel config (CONFIG_PREEMPT_NONE=y), or implementing rseq (restartable sequences) in the application's hot functions. See: https://lore.kernel.org/all/20260403191942.21410-1-dipiets@amazon.it/T/#t";

/// Fires when kernel >= 7 and CONFIG_PREEMPT_LAZY=y.
/// Based on: https://lore.kernel.org/all/20260403191942.21410-1-dipiets@amazon.it/T/#t
pub struct PreemptLazyDetectedRule;

impl MultiDataAnalyze for PreemptLazyDetectedRule {
fn analyze(
&self,
findings: &mut HashMap<String, DataFindings>,
all_processed_data: &HashMap<String, &ProcessedData>,
processed_data_accessor: &mut ProcessedDataAccessor,
) {
let systeminfo = match all_processed_data.get(SYSTEMINFO) {
Some(d) => *d,
None => return,
};
let kconfig = match all_processed_data.get(KERNEL_CONFIG) {
Some(d) => *d,
None => return,
};

for run_name in systeminfo.runs.keys() {
let version = match processed_data_accessor.key_value_value_by_key(
systeminfo,
run_name,
"Kernel Version",
) {
Some(v) => v,
None => continue,
};

let major = version
.split('.')
.next()
.and_then(|m| m.parse::<u32>().ok())
.unwrap_or(0);
if major < 7 {
continue;
}

let is_lazy = processed_data_accessor
.key_value_value_by_key(kconfig, run_name, "CONFIG_PREEMPT_LAZY")
.map(|v| v == "y")
.unwrap_or(false);
if !is_lazy {
continue;
}

let desc = format!(
"Kernel {} in {} is running with PREEMPT_LAZY preemption model.",
version, run_name
);
findings
.entry(KERNEL_CONFIG.to_string())
.or_insert(DataFindings::default())
.insert_finding(
run_name,
"CONFIG_PREEMPT_LAZY",
AnalyticalFinding::new(
"PREEMPT_LAZY Detected".to_string(),
Score::Neutral.as_f64(),
desc,
PREEMPT_LAZY_MESSAGE.to_string(),
),
);
}
}
}

pub fn get_multi_data_rules() -> Vec<MultiDataAnalyticalRule> {
vec![]
vec![MultiDataAnalyticalRule::PreemptLazyDetectedRule(
PreemptLazyDetectedRule,
)]
}

#[cfg(test)]
mod tests {
use super::*;
use crate::data::common::data_formats::{AperfData, DataFormat, KeyValueData, KeyValueGroup};

fn kv_data(runs: Vec<(&str, Vec<(&str, &str)>)>) -> ProcessedData {
let mut pd = ProcessedData::new("test".to_string());
pd.data_format = DataFormat::KeyValue;
for (run, pairs) in runs {
let mut kv = KeyValueData::default();
let mut group = KeyValueGroup::default();
for (k, v) in pairs {
group.key_values.insert(k.to_string(), v.to_string());
}
kv.key_value_groups.insert(String::new(), group);
pd.runs.insert(run.to_string(), AperfData::KeyValue(kv));
}
pd
}

fn run_rule(sysinfo: &ProcessedData, kconfig: &ProcessedData) -> HashMap<String, DataFindings> {
let mut all: HashMap<String, &ProcessedData> = HashMap::new();
all.insert(SYSTEMINFO.to_string(), sysinfo);
all.insert(KERNEL_CONFIG.to_string(), kconfig);
let mut findings = HashMap::new();
let mut acc = ProcessedDataAccessor::new();
PreemptLazyDetectedRule.analyze(&mut findings, &all, &mut acc);
findings
}

#[test]
fn triggers_k7_preempt_lazy() {
let si = kv_data(vec![("r1", vec![("Kernel Version", "7.0.1")])]);
let kc = kv_data(vec![("r1", vec![("CONFIG_PREEMPT_LAZY", "y")])]);
assert!(run_rule(&si, &kc).contains_key(KERNEL_CONFIG));
}

#[test]
fn skips_k6() {
let si = kv_data(vec![("r1", vec![("Kernel Version", "6.12.0")])]);
let kc = kv_data(vec![("r1", vec![("CONFIG_PREEMPT_LAZY", "y")])]);
assert!(!run_rule(&si, &kc).contains_key(KERNEL_CONFIG));
}

#[test]
fn skips_no_preempt_lazy() {
let si = kv_data(vec![("r1", vec![("Kernel Version", "7.0.1")])]);
let kc = kv_data(vec![("r1", vec![])]);
assert!(!run_rule(&si, &kc).contains_key(KERNEL_CONFIG));
}
}
Loading