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
52 changes: 51 additions & 1 deletion src/analytics/rules/vmstat.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,54 @@
use crate::analytics::rule_templates::time_series_data_point_threshold_rule::time_series_data_point_threshold;
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::{
AnalyticalRule, Score, TimeSeriesDataPointThresholdRule, TimeSeriesStatRunComparisonRule,
TimeSeriesStatThresholdRule,
};
use crate::computations::{Comparator, Stat};
use crate::data::vmstat::Vmstat;
use crate::data::AnalyzeData;

impl AnalyzeData for Vmstat {}
impl AnalyzeData for Vmstat {
fn get_analytical_rules(&self) -> Vec<AnalyticalRule> {
vec![
time_series_data_point_threshold!(
name: "Major Page Faults Detected",
metric: "pgmajfault",
comparator: Comparator::Greater,
threshold: 0.0,
score: Score::Poor,
message: "Major page faults require disk I/O to resolve and can severely impact latency. Investigate memory pressure or insufficient page cache.",
),
time_series_stat_run_comparison!(
name: "Increased Minor Page Faults",
metric: "pgminorfault",
stat: Stat::Average,
comparator: Comparator::GreaterEqual,
abs: false,
delta_ratio: 0.2,
score: Score::Concerning,
message: "A significant increase in minor page faults may indicate changes in memory access patterns or working set size.",
),
time_series_stat_run_comparison!(
name: "Increased Major Page Faults",
metric: "pgmajfault",
stat: Stat::Average,
comparator: Comparator::GreaterEqual,
abs: false,
delta_ratio: 0.2,
score: Score::Poor,
message: "A significant increase in major page faults indicates growing disk-backed paging activity, which degrades performance.",
),
time_series_stat_threshold!(
name: "Excessive Page Fault Rate",
metric: "pgfault",
stat: Stat::P99,
comparator: Comparator::Greater,
threshold: 100000.0,
score: Score::Concerning,
message: "Very high page fault rate (>100k/s at P99) may indicate memory thrashing or an application allocating memory excessively.",
),
]
}
}
19 changes: 18 additions & 1 deletion src/data/vmstat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ impl ProcessData for Vmstat {
};
time_series_data_processor.proceed_to_time(raw_value.time);

let mut pgfault_delta: Option<f64> = None;
let mut pgmajfault_delta: Option<f64> = None;

for line in raw_value.data.lines() {
let mut split = line.split_whitespace();
let name = match split.next() {
Expand All @@ -79,10 +82,24 @@ impl ProcessData for Vmstat {
if name.contains("nr_") {
time_series_data_processor.add_data_point(&name, "values", val as f64);
} else {
time_series_data_processor
let delta = time_series_data_processor
.add_accumulative_data_point(&name, "values", val as f64);
match name {
"pgfault" => pgfault_delta = delta,
"pgmajfault" => pgmajfault_delta = delta,
_ => {}
}
}
}

// Derive pgminorfault = pgfault - pgmajfault
if let (Some(fault), Some(majfault)) = (pgfault_delta, pgmajfault_delta) {
time_series_data_processor.add_data_point(
"pgminorfault",
"values",
fault - majfault,
);
}
}

let time_series_data = time_series_data_processor.get_time_series_data();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,29 @@ function getStatisticalFindings(
(finding) =>
stats.includes(finding.stat) &&
dataTypes.includes(finding.dataType) &&
isFindingTypeExpected(finding.score, findingTypes),
isFindingTypeExpected(finding.baseScore, findingTypes),
);
}

/**
* The common column definitions to be used by the global and per-data statistical finding table.
* If the base value is 0, then it is a raw delta so it is treated as +/- infinity
* If both are raw delta, they are compared normally
*/
function compareDelta(a: StatisticalFinding, b: StatisticalFinding): number {
const aDelta = a.baseValue === 0 ? (a.delta > 0 ? Infinity : -Infinity) : a.delta;
const bDelta = b.baseValue === 0 ? (b.delta > 0 ? Infinity : -Infinity) : b.delta;
if (aDelta === bDelta) return b.delta - a.delta;
return bDelta - aDelta;
}

const COMMON_COLUMN_DEFINITIONS = [
{
id: "stat",
header: "Stat",
cell: (item: StatisticalFinding) => item.stat,
sortingComparator: (a: StatisticalFinding, b: StatisticalFinding) => {
if (a.stat == b.stat) return Math.abs(b.score) - Math.abs(a.score);
if (a.stat == b.stat) return compareDelta(a, b);
return a.stat.localeCompare(b.stat);
},
width: 80,
Expand All @@ -66,19 +75,21 @@ const COMMON_COLUMN_DEFINITIONS = [
id: "delta",
header: "Delta",
cell: (item: StatisticalFinding) => item.deltaString,
sortingComparator: (a: StatisticalFinding, b: StatisticalFinding) => Math.abs(b.score) - Math.abs(a.score),
sortingComparator: compareDelta,
width: 100,
},
{
id: "stat_value",
header: "Value",
cell: (item: StatisticalFinding) => formatNumber(item.statValue),
sortingComparator: (a: StatisticalFinding, b: StatisticalFinding) => b.statValue - a.statValue,
width: 80,
},
{
id: "base_value",
header: "Base",
cell: (item: StatisticalFinding) => formatNumber(item.baseValue),
sortingComparator: (a: StatisticalFinding, b: StatisticalFinding) => b.baseValue - a.baseValue,
width: 80,
},
{
Expand Down
8 changes: 8 additions & 0 deletions src/report_frontend/src/definitions/data-descriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,14 @@ export const DATA_DESCRIPTIONS: { [key in DataType]: DataDescription } = {
description:
"Page faults that require loading data from storage devices indicating memory pressure and I/O activity.",
desired: "lower",
helpfulLinks: ["https://www.kernel.org/doc/html/latest/admin-guide/mm/concepts.html#page-faults"],
},
pgminorfault: {
readableName: "Minor Page Faults",
description:
"Page faults resolved without disk I/O where the page is already present in memory. Derived as total page faults minus major page faults.",
desired: "lower",
helpfulLinks: ["https://www.kernel.org/doc/html/latest/admin-guide/mm/concepts.html#page-faults"],
},
compact_migrate_scanned: {
readableName: "Compact Migrate Scanned",
Expand Down
14 changes: 9 additions & 5 deletions src/report_frontend/src/utils/analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,14 @@ export interface StatisticalFinding {
*/
readonly stat: Stat;
/**
* Indicates how good or bad the delta is, computed from the
* desired value set in data-descriptions.ts
* Sign of the finding: -1 (negative / worse than base), 1 (positive / better than base),
* or 0 (neutral), based on the desired direction set in data-descriptions.ts.
*/
readonly score: number;
readonly baseScore: number;
/**
* The raw delta between the current and base stat values, used for sorting.
*/
readonly delta: number;
/**
* Color-coded string to be rendered that reflects the score of the delta
*/
Expand Down Expand Up @@ -167,7 +171,6 @@ export function computeAllTimeSeriesStatsDelta() {
color = "";
}

const score = baseScore * Math.abs(delta);
// If delta is 0, the two stats are the same, so we do not produce a delta string
const deltaString = delta != 0 ? <span style={{ color }}>{deltaStr}</span> : undefined;

Expand All @@ -177,7 +180,8 @@ export function computeAllTimeSeriesStatsDelta() {
runName,
metricName,
stat,
score,
baseScore,
delta,
deltaString,
baseValue,
statValue,
Expand Down
Loading