From 439aff7a278f311cc3b93231937ac6f1ebadda24 Mon Sep 17 00:00:00 2001 From: Salvatore Dipietro <116039223+salvatoredipietro@users.noreply.github.com> Date: Thu, 21 May 2026 18:45:02 +0200 Subject: [PATCH 1/2] Add pgminorfault metrics and analytics --- src/analytics/rules/vmstat.rs | 52 ++++++++++++++++++- src/data/vmstat.rs | 19 ++++++- .../src/definitions/data-descriptions.ts | 8 +++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/analytics/rules/vmstat.rs b/src/analytics/rules/vmstat.rs index 65b23c3c..89c5ed56 100644 --- a/src/analytics/rules/vmstat.rs +++ b/src/analytics/rules/vmstat.rs @@ -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 { + 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.", + ), + ] + } +} diff --git a/src/data/vmstat.rs b/src/data/vmstat.rs index d25025db..cbac169f 100644 --- a/src/data/vmstat.rs +++ b/src/data/vmstat.rs @@ -58,6 +58,9 @@ impl ProcessData for Vmstat { }; time_series_data_processor.proceed_to_time(raw_value.time); + let mut pgfault_delta: Option = None; + let mut pgmajfault_delta: Option = None; + for line in raw_value.data.lines() { let mut split = line.split_whitespace(); let name = match split.next() { @@ -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(); diff --git a/src/report_frontend/src/definitions/data-descriptions.ts b/src/report_frontend/src/definitions/data-descriptions.ts index cc4201d7..6980cf25 100644 --- a/src/report_frontend/src/definitions/data-descriptions.ts +++ b/src/report_frontend/src/definitions/data-descriptions.ts @@ -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", From 42aaa50f5f4f2d7a5adc3cae31319a632318bbe8 Mon Sep 17 00:00:00 2001 From: lancelui-amzn Date: Thu, 21 May 2026 11:46:10 -0500 Subject: [PATCH 2/2] bugfix: statistical finding sorting by score instead of delta --- .../analytics/StatisticalFindings.tsx | 17 ++++++++++++++--- src/report_frontend/src/utils/analytics.tsx | 14 +++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/report_frontend/src/components/analytics/StatisticalFindings.tsx b/src/report_frontend/src/components/analytics/StatisticalFindings.tsx index 007b4e84..74f65ddf 100644 --- a/src/report_frontend/src/components/analytics/StatisticalFindings.tsx +++ b/src/report_frontend/src/components/analytics/StatisticalFindings.tsx @@ -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, @@ -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, }, { diff --git a/src/report_frontend/src/utils/analytics.tsx b/src/report_frontend/src/utils/analytics.tsx index b23c099b..d096a6b0 100644 --- a/src/report_frontend/src/utils/analytics.tsx +++ b/src/report_frontend/src/utils/analytics.tsx @@ -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 */ @@ -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 ? {deltaStr} : undefined; @@ -177,7 +180,8 @@ export function computeAllTimeSeriesStatsDelta() { runName, metricName, stat, - score, + baseScore, + delta, deltaString, baseValue, statValue,