From bf125e00f66e83fd8564f6d51f373b2c69323ee8 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 27 Aug 2026 19:21:50 +0800 Subject: [PATCH] chore(style): expose invalidation fallback telemetry --- moli-renderer-v8/src/style_engine/outcome.rs | 27 +++++- .../src/style_engine/target_result.rs | 84 ++++++++++++++++++- .../src/style_engine/tests/outcome.rs | 32 +++++++ 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/moli-renderer-v8/src/style_engine/outcome.rs b/moli-renderer-v8/src/style_engine/outcome.rs index d2fcc98935..f3a9174f24 100644 --- a/moli-renderer-v8/src/style_engine/outcome.rs +++ b/moli-renderer-v8/src/style_engine/outcome.rs @@ -19,10 +19,11 @@ use super::{ target_result::{ StyleInvalidationCleanupClassConstraint, StyleInvalidationDiagnosticTargetResult, StyleInvalidationDiagnosticTargetResultSummaryTraceCountsSink, - StyleInvalidationRetainedCleanupPlanSink, StyleInvalidationRetainedCleanupRoots, - StyleInvalidationRetainedCleanupRootsSink, StyleInvalidationRetainedCleanupSink, - StyleInvalidationRetainedTargetResults, StyleInvalidationRetainedTargetResultsSink, - StyleInvalidationRetainedTargetResultsTrace, retained_target_results_for_source_results, + StyleInvalidationFallbackTelemetry, StyleInvalidationRetainedCleanupPlanSink, + StyleInvalidationRetainedCleanupRoots, StyleInvalidationRetainedCleanupRootsSink, + StyleInvalidationRetainedCleanupSink, StyleInvalidationRetainedTargetResults, + StyleInvalidationRetainedTargetResultsSink, StyleInvalidationRetainedTargetResultsTrace, + retained_target_results_for_source_results, }, }; @@ -389,6 +390,11 @@ impl FinalizedStyleInvalidationResult { ) -> &StyleInvalidationDiagnosticTargetResultSummary { self.cleanup.trace.diagnostic_target_result_summary() } + + #[cfg(test)] + pub(super) fn fallback_telemetry(&self) -> StyleInvalidationFallbackTelemetry { + self.cleanup.trace.fallback_telemetry() + } } impl StyleInvalidationFinalCleanup { @@ -564,6 +570,7 @@ impl StyleInvalidationFinalCleanup { fn trace_drain_attempt(&self, summary: &StyleInvalidationDrainSummary) { let mut drain_fields = StyleInvalidationDrainTraceFields::default(); summary.record_trace_fields_into(&mut drain_fields); + let fallback_telemetry = self.trace.fallback_telemetry(); let mut diagnostic_target_result_counts = StyleInvalidationDrainDiagnosticTargetResultCounts::default(); self.trace @@ -591,6 +598,14 @@ impl StyleInvalidationFinalCleanup { source_scope_fallback_roots = ?drain_fields.source_scope_fallback_roots, clear_shadow_cascade_data_for_cleanup_target = self.effects.clears_shadow_cascade_data_for_cleanup_target(), + exact_target_result_count = fallback_telemetry.exact_target_result_count(), + inexact_target_result_count = fallback_telemetry.inexact_target_result_count(), + fallback_target_result_count = + fallback_telemetry.fallback_target_result_count(), + fallback_reason_hit_count = fallback_telemetry.fallback_reason_hit_count(), + fallback_reason_target_counts = ?fallback_telemetry.fallback_reason_target_counts(), + source_result_affected_root_count = + fallback_telemetry.source_result_affected_root_count(), retained_source_unavailable_target_count = diagnostic_target_result_counts.retained_source_unavailable_target_count, missing_fallback_roots_target_count = @@ -693,6 +708,10 @@ impl StyleInvalidationCleanupTrace { self.retained_target_results.diagnostic_target_results() } + fn fallback_telemetry(&self) -> StyleInvalidationFallbackTelemetry { + self.retained_target_results.fallback_telemetry() + } + fn record_diagnostic_target_result_counts_into( &self, sink: &mut impl StyleInvalidationDiagnosticTargetResultSummaryTraceCountsSink, diff --git a/moli-renderer-v8/src/style_engine/target_result.rs b/moli-renderer-v8/src/style_engine/target_result.rs index d647cb5846..c773eaf420 100644 --- a/moli-renderer-v8/src/style_engine/target_result.rs +++ b/moli-renderer-v8/src/style_engine/target_result.rs @@ -1,4 +1,4 @@ -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use moli_selector::{ MoliInvalidationResult, MoliInvalidationSourceResultsSink, StyloSourceAffectedRootsCleanup, StyloSourceAffectedRootsCleanupSink, @@ -102,6 +102,20 @@ pub(super) struct StyleInvalidationDiagnosticTargetResult { affected_root_count: usize, } +/// Aggregate fallback telemetry for one invalidation drain. +/// +/// This is derived from retained diagnostic target results only when style +/// invalidation tracing is enabled (and in tests). Normal rendering does not +/// retain the target results or maintain counters alongside mutations. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub(super) struct StyleInvalidationFallbackTelemetry { + exact_target_result_count: usize, + inexact_target_result_count: usize, + fallback_target_result_count: usize, + source_result_affected_root_count: usize, + fallback_reason_target_counts: IndexMap, +} + #[derive(Clone, Debug, Default, PartialEq, Eq)] pub(super) struct StyleInvalidationDiagnosticTargetResultSummary { diagnostic_target_result_count: usize, @@ -302,6 +316,70 @@ impl StyleInvalidationDiagnosticTargetResult { } } +impl StyleInvalidationFallbackTelemetry { + fn from_target_results(results: &[StyleInvalidationDiagnosticTargetResult]) -> Self { + let mut telemetry = Self::default(); + for result in results { + if result.exact { + telemetry.exact_target_result_count += 1; + } else { + telemetry.inexact_target_result_count += 1; + } + if !result.fallback_reasons.is_empty() { + telemetry.fallback_target_result_count += 1; + } + telemetry.source_result_affected_root_count += result.affected_root_count; + // A source result stores reasons in an IndexSet, so each reason is + // counted at most once per source target. Selector duplication + // therefore cannot inflate the fallback hit rate. + for reason in &result.fallback_reasons { + *telemetry + .fallback_reason_target_counts + .entry(*reason) + .or_default() += 1; + } + } + telemetry + } + + pub(super) fn exact_target_result_count(&self) -> usize { + self.exact_target_result_count + } + + pub(super) fn inexact_target_result_count(&self) -> usize { + self.inexact_target_result_count + } + + pub(super) fn fallback_target_result_count(&self) -> usize { + self.fallback_target_result_count + } + + pub(super) fn source_result_affected_root_count(&self) -> usize { + self.source_result_affected_root_count + } + + pub(super) fn fallback_reason_hit_count(&self) -> usize { + self.fallback_reason_target_counts.values().sum() + } + + pub(super) fn fallback_reason_target_counts( + &self, + ) -> &IndexMap { + &self.fallback_reason_target_counts + } + + #[cfg(test)] + pub(super) fn fallback_reason_target_count( + &self, + reason: StyloSourceInvalidationFallbackReason, + ) -> usize { + self.fallback_reason_target_counts + .get(&reason) + .copied() + .unwrap_or_default() + } +} + impl StyleDiagnosticSourceTargetAvailability { fn with_fallback_roots(fallback_roots: Option) -> Self { Self { @@ -381,6 +459,10 @@ impl StyleInvalidationRetainedTargetResultsTrace { &self.diagnostic_target_results } + pub(super) fn fallback_telemetry(&self) -> StyleInvalidationFallbackTelemetry { + StyleInvalidationFallbackTelemetry::from_target_results(&self.diagnostic_target_results) + } + pub(super) fn record_diagnostic_target_result_counts_into( &self, sink: &mut impl StyleInvalidationDiagnosticTargetResultSummaryTraceCountsSink, diff --git a/moli-renderer-v8/src/style_engine/tests/outcome.rs b/moli-renderer-v8/src/style_engine/tests/outcome.rs index f920d96767..86c725e2ec 100644 --- a/moli-renderer-v8/src/style_engine/tests/outcome.rs +++ b/moli-renderer-v8/src/style_engine/tests/outcome.rs @@ -700,6 +700,17 @@ fn finalized_cleanup_preserves_mixed_subtree_sources() { StyleInvalidationCleanupClass::UnknownOrFallback ); assert_eq!(application.affected_root_count(), 3); + let telemetry = application.fallback_telemetry(); + assert_eq!(telemetry.exact_target_result_count(), 2); + assert_eq!(telemetry.inexact_target_result_count(), 1); + assert_eq!(telemetry.fallback_target_result_count(), 1); + assert_eq!(telemetry.source_result_affected_root_count(), 2); + assert_eq!( + telemetry.fallback_reason_target_count( + StyloSourceInvalidationFallbackReason::UnsupportedDependency, + ), + 1 + ); assert!(matches!( application.cleanup_target(), StyleInvalidationCleanupTarget::MixedSubtreeRoots(groups) @@ -1412,6 +1423,27 @@ fn fallback_only_source_emits_each_target_result_fallback_reason() { .fallback_reasons() .contains(&StyloSourceInvalidationFallbackReason::UnsupportedStateDependency) ); + let telemetry = application.fallback_telemetry(); + assert_eq!(telemetry.exact_target_result_count(), 0); + assert_eq!(telemetry.inexact_target_result_count(), 1); + assert_eq!(telemetry.fallback_target_result_count(), 1); + assert_eq!(telemetry.source_result_affected_root_count(), 1); + assert_eq!( + telemetry.fallback_reason_target_count( + StyloSourceInvalidationFallbackReason::UnsupportedDependency, + ), + 1 + ); + assert_eq!( + telemetry.fallback_reason_target_count( + StyloSourceInvalidationFallbackReason::UnsupportedStateDependency, + ), + 1 + ); + assert_eq!( + telemetry.fallback_reason_target_count(StyloSourceInvalidationFallbackReason::FullSelector), + 0 + ); } #[test] fn structural_boundary_fallback_target_queries_dedupe_duplicate_targets() {