FrameGuard logo

FrameGuard

Performance regressions, testable.
Automated Flutter UI performance regression detection — budgets, baselines, and evidence-backed reports you can enforce in CI.

pub package pub points likes CI Pages license

stars issues PRs Flutter Dart docs thanks.dev Sponsor

Docs · pub.dev · CLI · Changelog · Sponsor

FrameGuard banner


Status

Version 0.6.0
Repository github.com/theworker02/frameguard
Package pub.dev/packages/frameguard
Docs site theworker02.github.io/frameguard
License MIT
Telemetry None — local by default

Why FrameGuard?

Flutter already has excellent profilers. DevTools answers:

What happened during this profiling session?

FrameGuard answers:

Did performance regress, where, why is that likely, and should this build fail?

DevTools FrameGuard
Goal Interactive exploration Automation & regression gates
Output Timelines you interpret Budgets, baselines, matchers, CI exit codes
Audience Humans in a profiling session Humans and pipelines
Telemetry N/A None — local by default

FrameGuard is not a thin DevTools wrapper. It turns documented Flutter frame timings into reproducible explanations.


Features

  • Session capture via SchedulerBinding.addTimingsCallback / FrameTiming
  • Refresh-rate-aware budgets (60 / 90 / 120 / 144 Hz — no hardcoded 16.67 ms dogma)
  • Jank severity (healthy / minor / major / severe)
  • Percentiles — p50 / p90 / p95 / p99, histograms, streaks
  • Build vs raster classification (derived, never claimed as certainty)
  • Regions & rebuild counts (FrameGuardRegion)
  • Traces, markers, sync tasks
  • Explainability + recommendations tied to evidence (FG001FG010)
  • JSON / text / HTML reports (versioned schema)
  • CSV · JUnit · SARIF · Markdown exporters for CI / PR comments
  • Baselines & golden files (never silently overwritten)
  • Multi-run statistics (median, MAD, CI; outliers flagged, not deleted)
  • Test matchers + FrameGuardTest.measure
  • CLI for compare / check / export / summary / doctor / baseline / history / suggest-budget
  • Local history (JSONL) for gradual drift without a backend
  • Reusable GitHub Action (.github/actions/frameguard-check)
  • Optional overlay & runtime budget watcher
  • Capability model — Unavailable beats fake zeros

Install

# pubspec.yaml
dependencies:
  frameguard: ^0.6.0

For tests-only usage you can keep it under dev_dependencies. Test helpers live in a separate library:

import 'package:frameguard/frameguard.dart';
import 'package:frameguard/frameguard_test.dart';
flutter pub get

Quick start

import 'package:flutter/material.dart';
import 'package:frameguard/frameguard.dart';

void main() {
  FrameGuard.initialize(
    config: FrameGuardConfig(
      samplingMode: SamplingMode.balanced,
      defaultBudget: FrameBudget.forRefreshRate(60, maxJankRate: 0.02),
    ),
  );

  runApp(
    const FrameGuardScope(
      child: FrameGuardOverlay(
        compact: true,
        child: MyApp(),
      ),
    ),
  );
}

Capture a session

final session = FrameGuard.startSession(name: 'home_scroll');
// …interact with the app…
final report = await session.stop();

debugPrint(report.summary());
await report.writeJson(File('reports/home_scroll.json'));
await report.writeHtml(File('reports/home_scroll.html'));

Assert in tests

testWidgets('product list stays within budget', (tester) async {
  await tester.pumpWidget(const App());

  final report = await FrameGuardTest.measure(
    tester,
    name: 'product_list',
    action: () async {
      await tester.fling(find.byType(ListView), const Offset(0, -1000), 1000);
      await tester.pumpAndSettle();
    },
  );

  expect(
    report,
    meetsFrameBudget(
      FrameBudget.forRefreshRate(
        60,
        maxJankFrames: 2,
        maxJankRate: 0.01,
      ),
    ),
  );
});

Example FrameGuard report


Budgets & baselines

final budget = FrameBudget(
  maxJankFrames: 2,
  maxJankRate: 0.01,
  maxP95FrameTime: const Duration(milliseconds: 16),
  maxP99FrameTime: const Duration(milliseconds: 24),
);

final evaluation = report.evaluate(budget);
if (!evaluation.passed) {
  fail(evaluation.summary());
}

Optional project YAML (Dart config remains primary):

cp frameguard.yaml.example frameguard.yaml
dart run frameguard config validate

Update baselines deliberately:

dart run frameguard baseline update reports/catalog.json --out baselines/catalog.json
dart run frameguard check reports/current.json --baseline baselines/catalog.json

Exit codes: 0 pass · 1 regression · 2 invalid config/report.


CLI

dart run frameguard help
dart run frameguard init
dart run frameguard doctor
dart run frameguard list
dart run frameguard explain report.json
dart run frameguard frames report.json --janky-only
dart run frameguard top report.json
dart run frameguard check reports/ --baseline baselines/ --profile mid_range --require-profile
dart run frameguard suggest-budget reports/home.json --format dart
dart run frameguard history append reports/home.json
dart run frameguard summary report.json --format markdown
dart run frameguard batch reports/ --format junit --out-dir reports/exports
dart run frameguard watch reports/ --once
dart run frameguard completions --shell bash

Full command map: doc/cli.md · docs/cli.html.


CI

Gate performance reports in GitHub Actions:

- uses: theworker02/frameguard/.github/actions/frameguard-check@main
  with:
    report: reports/
    baseline: baselines/
    profile: mid_range
    require-profile: 'true'

Or call the CLI directly:

dart run frameguard check reports/ --baseline baselines/ --require-profile --profile mid_range
dart run frameguard summary reports/catalog.json --format markdown >> "$GITHUB_STEP_SUMMARY"

More: doc/ci.md.


Regions, traces, markers

FrameGuardRegion(
  name: 'product_grid',
  child: ProductGrid(),
);

await FrameGuard.trace('open_product', () async {
  await Navigator.of(context).push(...);
});

FrameGuard.mark('products_loaded', metadata: {'count': 42});

await FrameGuard.measureTask('parse_catalog', () => parseCatalog(data));

Scenarios & statistics

final result = await FrameScenarioRunner(
  scenario: const FrameScenario(name: 'catalog_scroll', warmupFrames: 30),
  runs: 5,
  budget: FrameBudget.forRefreshRate(120),
).runAggregated((i) async {
  await scrollCatalog();
});

debugPrint(result.summary());

Details: doc/scenarios.md.


Platform support

Platform Frame timings Image cache Overlay Notes
Android / iOS Yes Yes Yes Prefer profile for gates
Desktop Yes Yes Yes
Web Best-effort Best-effort Yes Absolute ms budgets vary by browser

Native extras (JankStats, signposts, GPU counters) are Unavailable until real adapters ship — we will not invent zeros. See FrameGuard.capabilities and platform adapters.


Privacy

  • No analytics
  • No telemetry
  • No accounts
  • No required dashboard
  • Reports stay on disk under your control

Documentation

Product site Guide, CLI, diagnostics, brand
Brand kit Voice, color, logo
Docs index Package guides
API cookbook Common workflows
CI integration Pipelines & Action
Diagnostics FG001–FG010 What each finding means
Contributing Dev workflow
Changelog SemVer history
Security Vulnerability reporting
Code of Conduct Community norms
Sponsor thanks.dev / GitHub Sponsors

API reference (after publish): pub.dev/documentation/frameguard


Example & benchmarks

cd example && flutter run --profile
flutter test
flutter test benchmark/overhead_benchmark.dart

The example app includes intentional jank scenarios (rebuild storm, CPU stall, raster stress, …) so you can validate FrameGuard against known behavior.


Roadmap (honest)

Shipped foundations: sessions, budgets, baselines, CI, explainability, exporters, scenario stats, local history, budget suggestions.

Next (when evidence exists — never as fake features):

  • Deeper GC / memory correlation where public APIs allow
  • Android JankStats / iOS signpost adapters
  • DevTools extension
  • Historical trend dashboards (still local-first)

Contributing

PRs welcome. Please read CONTRIBUTING.md and the Code of Conduct.

dart format .
flutter analyze
flutter test

Bug reports & ideas: Issues


License

MIT — see LICENSE.

Support development


FrameGuard — make performance regressions testable the way functional regressions already are.

Libraries

frameguard
FrameGuard — automated Flutter performance regression detection.
frameguard_test
FrameGuard test helpers, matchers, and synthetic jank utilities.