Skip to content

2.0: performance measurement windows and per-metric state (#769, #770) - #800

Merged
casoon merged 1 commit into
mainfrom
feat/v2-performance-measurement
Oct 26, 2026
Merged

casoon merged 1 commit into
mainfrom
feat/v2-performance-measurement

Conversation

@casoon

@casoon casoon commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Package 2, part 2: performance. This builds on the collection contract from #799.

#770 Measurement windows

  • LCP: the last candidate before the read, including candidates painted after load. The old cut-off at load + 100 ms kept the small initial text as LCP.
  • TTI: a lab proxy of the Lighthouse definition. It is the end of the last long task before the first 5 s quiet window after FCP, and at least domInteractive. It stays marked estimated_lab because network quiet is not observed.
  • TBT: the blocking time of long tasks over FCP→TTI. Each task is clipped to that window, and the part beyond 50 ms counts. It is computed in Rust from the raw long-task list. Before, tasks were summed up to load + 100 ms.
  • Collection end: collection stops after 1 s without a new long task (Lighthouse cpuQuietThresholdMs), or after 8 s at most.
  • Speed Index: stays a diagnostic estimate without score weight.
  • Removed tbt_zero_heavy_page: the warning claimed TBT = 0 with LCP > 3 s is impossible. That is false: a static page on a slow network has no long tasks. The locale keys are removed too.

#769 Measurement evidence

  • Records:
    • Each core metric records its own state: performance.lcp, performance.fcp, performance.cls, performance.tbt. The states are measured, failed (observer_not_installed, metric_not_captured, fcp_not_captured, invalid_metric_value) and unsupported (entry_type_not_supported).
    • The observers, CDP metrics and navigation timing record their state as well.
  • Sub-analyses decode strictly: content weight, render blocking, third party, critical chain, minification and animations. A failed read is no 0-byte page and no clean list. Content weight feeds the score, so losing it makes performance partial.
  • Coverage: with no profiler answer, JS coverage reports used_pct: null and js_coverage_unavailable instead of 100 % used.
  • Third-party isolation: an origin whose isolation pass failed makes the collection partial or failed instead of silently going missing.
  • Throttled profiles:
    • A profile whose CPU throttling or cache disabling failed is no longer stored under that profile's name.
    • A score requires all four core metrics; otherwise it is score: null, reported as "incomplete" in the PDF.
    • Only a complete LhMobile profile becomes canonical. The old min(50) score for a missing LCP is gone.

Tests

  • Unit tests:
    • TTI quiet window;
    • TBT FCP→TTI clipping;
    • late task after load;
    • slow static page without a warning;
    • per-metric states, including no observer, unsupported and NaN;
    • coverage and isolation states.
  • New browser tests: lcp_includes_a_hero_rendered_after_load, tbt_counts_a_long_task_after_load and slow_static_page_measures_zero_blocking_time (Slow 3G with a render-blocking stylesheet and no script).
  • The existing foundation_missing_observers_are_not_perfect_zero_metrics test now also checks the reasons.

Validation (local, all green)

  • fmt passes, and clippy -D warnings is clean with all features and with no default features.
  • cargo test --all-features passes.
  • The ignored suites CI runs all pass:
    • integration_test, 45/45;
    • detection_corpus_test, schema_rules_detection_corpus_test, vulnerable_libs_detection_corpus_test;
    • easy_language_detection_test, help_mechanisms_detection_test, score_calibration_test.
  • reference_sites_test shows no deviation.

Browser runs take a little longer because of the 1 s CPU-quiet wait. The detection corpus went from about 400 s to 463 s.

…ction state (#769, #770)

LCP keeps the last candidate, also after load; TTI is the end of the last
long task before the first 5 s quiet window after FCP; TBT sums long-task
blocking over FCP->TTI from the raw task list. The tbt_zero_heavy_page
warning is gone: a static page on a slow network has no long tasks.

Observers, CDP metrics, navigation timing, the four core metrics, every
sub-analysis, JS/CSS coverage and third-party isolation record their
state. A failed read is no 0-byte page, no clean resource list and no
100 % JS used. Throttled profiles need CPU throttling, a cold cache and
all four core metrics for a score and for canonical adoption.
@casoon
casoon merged commit cfd0e99 into main Oct 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant