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
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ tdigest = "0.2"
csv = "1.2"
csv-to-html = "0.5.10"
numeric-sort = "0.1.5"
regex = "1.12.3"

[target.'cfg(target_os = "linux")'.dependencies]
sysinfo = "0.26.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::fmt::Formatter;
pub struct TimeSeriesDataPointThresholdRule {
pub rule_name: &'static str,
pub metric_name: &'static str,
pub regex_matching: bool,
pub comparator: Comparator,
pub threshold: f64,
pub score: f64,
Expand All @@ -30,6 +31,7 @@ macro_rules! time_series_data_point_threshold {
TimeSeriesDataPointThresholdRule{
rule_name: $rule_name,
metric_name: $metric_name,
regex_matching: false,
comparator: $comparator,
threshold: $threshold,
score: $score.as_f64(),
Expand All @@ -40,6 +42,30 @@ macro_rules! time_series_data_point_threshold {
}
pub(crate) use time_series_data_point_threshold;

macro_rules! time_series_data_point_threshold_multi_metric {
{
name: $rule_name:literal,
pattern: $pattern:literal,
comparator: $comparator:path,
threshold: $threshold:literal,
score: $score:expr,
message: $message:literal,
} => {
AnalyticalRule::TimeSeriesDataPointThresholdRule(
TimeSeriesDataPointThresholdRule{
rule_name: $rule_name,
metric_name: $pattern,
regex_matching: true,
comparator: $comparator,
threshold: $threshold,
score: $score.as_f64(),
message: $message,
}
)
};
}
pub(crate) use time_series_data_point_threshold_multi_metric;

impl fmt::Display for TimeSeriesDataPointThresholdRule {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
Expand All @@ -58,50 +84,63 @@ impl Analyze for TimeSeriesDataPointThresholdRule {
processed_data_accessor: &mut ProcessedDataAccessor,
) {
for run_name in processed_data.runs.keys() {
// Produce one finding per metric for the data point with the largest absolute score
let mut max_abs_finding_score: f64 = 0.0;
let mut max_score_value: f64 = 0.0;
let mut rule_matched = false;
for &value in processed_data_accessor.time_series_metric_values_iterator(
processed_data,
run_name,
self.metric_name,
) {
if self.comparator.compare(value, self.threshold) {
rule_matched = true;
let abs_finding_score =
analytics::compute_finding_score(value, self.threshold, self.score).abs();
if abs_finding_score >= max_abs_finding_score {
max_score_value = value;
max_abs_finding_score = abs_finding_score;
let matched_metric_names = if self.regex_matching {
processed_data_accessor.time_series_matched_metric_names(
processed_data,
run_name,
self.metric_name,
)
} else {
vec![self.metric_name]
};

for metric_name in matched_metric_names {
// Produce one finding per metric for the data point with the largest absolute score
let mut max_abs_finding_score: f64 = 0.0;
let mut max_score_value: f64 = 0.0;
let mut rule_matched = false;
for &value in processed_data_accessor.time_series_metric_values_iterator(
processed_data,
run_name,
metric_name,
) {
if self.comparator.compare(value, self.threshold) {
rule_matched = true;
let abs_finding_score =
analytics::compute_finding_score(value, self.threshold, self.score)
.abs();
if abs_finding_score >= max_abs_finding_score {
max_score_value = value;
max_abs_finding_score = abs_finding_score;
}
}
}
}

if rule_matched {
let finding_score = if self.score > 0.0 {
max_abs_finding_score
} else {
max_abs_finding_score * -1.0
};
let finding_description = format!(
"At least one data point in {} is {} ({} the threshold of {}).",
run_name,
formatted_number_string(max_score_value),
self.comparator,
self.threshold
);
if rule_matched {
let finding_score = if self.score > 0.0 {
max_abs_finding_score
} else {
max_abs_finding_score * -1.0
};
let finding_description = format!(
"At least one data point in {} is {} ({} the threshold of {}).",
run_name,
formatted_number_string(max_score_value),
self.comparator,
self.threshold
);

data_findings.insert_finding(
run_name,
self.metric_name,
AnalyticalFinding::new(
self.rule_name.to_string(),
finding_score,
finding_description,
self.message.to_string(),
),
);
data_findings.insert_finding(
run_name,
metric_name,
AnalyticalFinding::new(
self.rule_name.to_string(),
finding_score,
finding_description,
self.message.to_string(),
),
);
}
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion src/analytics/rules/efa_stat.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::analytics::rule_templates::time_series_stat_run_comparison_rule::time_series_stat_run_comparison;
use crate::analytics::rule_templates::time_series_stat_threshold_rule::time_series_stat_threshold;
use crate::analytics::time_series_data_point_threshold_rule::time_series_data_point_threshold_multi_metric;
use crate::analytics::{
AnalyticalRule, Score, TimeSeriesStatRunComparisonRule, TimeSeriesStatThresholdRule,
AnalyticalRule, Score, TimeSeriesDataPointThresholdRule, TimeSeriesStatRunComparisonRule,
TimeSeriesStatThresholdRule,
};
use crate::computations::{Comparator, Stat};
use crate::data::efa_stat::EfaStat;
Expand Down Expand Up @@ -48,6 +50,14 @@ impl AnalyzeData for EfaStat {
score: Score::Critical,
message: "The average numbers of bytes transmitted by EFA are different. Verify if this is the expected behavior.",
),
time_series_data_point_threshold_multi_metric!(
name: "EFA Errors",
pattern: "^*_err$",
comparator: Comparator::GreaterEqual,
threshold: 1.0,
score: Score::Poor,
message: "One or more errors occurred in EFA driver. Click the info button to learn more about the error",
),
]
}
}
25 changes: 25 additions & 0 deletions src/data/common/processed_data_accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::computations::{f64_to_fixed_2, Statistics};
use crate::data::common::data_formats::{
AperfData, DataFormat, KeyValueData, ProcessedData, Series, TimeSeriesData, TimeSeriesMetric,
};
use regex::Regex;
use std::collections::HashMap;
use std::fmt::Write;

Expand Down Expand Up @@ -83,6 +84,30 @@ impl ProcessedDataAccessor {
))
}

/// Returns the list of metric names in the run that matches the pattern.
pub fn time_series_matched_metric_names<'a>(
&self,
processed_data: &'a ProcessedData,
run_name: &str,
pattern: &str,
) -> Vec<&'a str> {
let re = match Regex::new(pattern) {
Ok(re) => re,
Err(_) => return Vec::new(),
};

if let Some(time_series_data) = get_time_series_data(processed_data, run_name) {
time_series_data
.metrics
.keys()
.filter(|&key| re.is_match(key))
.map(|key| key.as_str())
.collect()
} else {
Vec::new()
}
}

/// Returns the value of the corresponding key in the key-value data.
pub fn key_value_value_by_key<'a>(
&self,
Expand Down
18 changes: 18 additions & 0 deletions tests/analytics/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub fn create_processed_data(data_name: &str, runs: Vec<(&str, AperfData)>) -> P
pub trait DataFindingsExt {
fn has_findings_for_run(&self, run_name: &str) -> bool;
fn num_runs_with_findings(&self) -> usize;
fn has_findings_for_metric(&self, run_name: &str, metric_name: &str) -> bool;
fn num_metrics_with_findings(&self, run_name: &str) -> usize;
}

impl DataFindingsExt for aperf::analytics::DataFindings {
Expand All @@ -105,4 +107,20 @@ impl DataFindingsExt for aperf::analytics::DataFindings {
.map(|obj| obj.len())
.unwrap_or(0)
}

fn has_findings_for_metric(&self, run_name: &str, metric_name: &str) -> bool {
let json = serde_json::to_value(self).unwrap();
json["per_run_findings"][run_name]["findings"]
.as_object()
.map(|obj| obj.contains_key(metric_name))
.unwrap_or(false)
}

fn num_metrics_with_findings(&self, run_name: &str) -> usize {
let json = serde_json::to_value(self).unwrap();
json["per_run_findings"][run_name]["findings"]
.as_object()
.map(|obj| obj.len())
.unwrap_or(0)
}
}
Loading
Loading