flutter_perf_radar 0.1.1 copy "flutter_perf_radar: ^0.1.1" to clipboard
flutter_perf_radar: ^0.1.1 copied to clipboard

On-device performance and stability tracer for Flutter. Frame timing, startup, error capture, and main-thread stall detection in debug/profile; complete no-op in release.

flutter_perf_radar #

pub.dev License: MIT

On-device performance and stability tracer for Flutter. Measures frame timing, detects jank, captures unhandled errors, watches for main-thread stalls, and counts per-subtree widget rebuilds — in debug and profile builds. Complete no-op in release — no guards, no build flavours required.


Installation #

dependencies:
  flutter_perf_radar: ^0.1.0

Quick start #

1. Initialise in main() #

import 'package:flutter_perf_radar/flutter_perf_radar.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await PerfRadar.init(PerfRadarConfig.standard());
  runApp(const MyApp());
}

PerfRadarConfig.standard() enables the engine in debug and profile builds (kDebugMode || kProfileMode) with a 250 ms stall threshold.

2. Add the overlay badge (optional) #

// In your root widget's build method:
return PerfRadar.overlay(child: const MyApp());

The draggable badge shows live frame count and jank count. Tap to open the full PerfRadarScreen dashboard.

3. Instrument custom operations #

// Synchronous — span is recorded automatically.
final result = PerfRadar.trace('parse_json', () => jsonDecode(raw));

// Async.
final user = await PerfRadar.traceAsync('fetch_user', () => api.getUser(id));

// Manual start/stop for callback-bounded code.
final handle = PerfRadar.start('image_decode');
decoder.decode(bytes, onDone: () => handle.stop());

4. Open the inspector screen #

Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => const PerfRadarScreen()),
);

The screen shows frame stats, span statistics with histograms, stability counters (errors + stalls), and per-key rebuild counts.


Frame timing and jank #

PerfRadar hooks into SchedulerBinding.addTimingsCallback. Every frame duration is recorded. Frames longer than jankThresholdMicros (default 16 667 µs ≈ 60 fps) are counted as jank.

final stats = PerfRadar.frameStats;
print('frames: ${stats.frameCount}  jank: ${stats.jankCount}');

Resetting the counters #

Frame and jank counts, the recent-frame ring, and the build/raster/total latency histograms all accumulate since launch. Reset them to zero to measure a specific interval — a single screen transition, a scroll, one network round-trip — instead of since-launch totals:

PerfRadar.resetFrameStats(); // start of the window
// ... exercise the code path you want to measure ...
final stats = PerfRadar.frameStats; // stats for just this interval

PerfRadar.resetFrameStats() clears the counters, the recent-frame ring, and every latency histogram; jankThresholdMicros is left untouched and the engine keeps running. It delegates to FrameStats.reset() on the underlying accumulator.

Three ways to trigger a reset:

  • ProgrammaticallyPerfRadar.resetFrameStats() from your own code.
  • From the dashboard — the Frames tab shows a reset button (FramesTab.onReset, wired by PerfRadarView to resetFrameStats() plus an immediate refresh) whenever it is displayed inside PerfRadarScreen.
  • Over the VM service — the ext.perf_radar.resetFrames extension (registered alongside ext.perf_radar.snapshot) calls PerfRadar.resetFrameStats() and acknowledges with {"reset": true}, so DevTools or any VM service client can zero the counters remotely.

All reset paths are no-ops in release builds.


Stability: errors and stall watchdog #

Unhandled errors are captured via FlutterError.onError and PlatformDispatcher.instance.onError:

final snapshot = PerfRadar.stabilitySnapshot;
print('errors: ${snapshot.errorCount}  stalls: ${snapshot.stallCount}');

for (final err in snapshot.recentErrors) {
  print('${err.timestamp}: ${err.summary}');
}

The stall watchdog fires a periodic heartbeat on the main isolate. When the heartbeat is delayed by more than stallThresholdMicros, a StallRecord is emitted and retained:

for (final stall in snapshot.recentStalls) {
  print('stall: ${stall.durationMicros} µs at ${stall.timestamp}');
}

TracedSubtree — rebuild counting #

Wrap any widget subtree to count how many times it rebuilds:

TracedSubtree(
  label: 'home_feed',
  child: const HomeFeed(),
)

Each rebuild increments the span counter for key rebuild:home_feed. Read counts from the span snapshot:

final snap = PerfRadar.snapshot();
for (final entry in snap.stats.entries) {
  if (entry.key.name.startsWith('rebuild:')) {
    print('${entry.key.name}: ${entry.value.histogram.count} rebuilds');
  }
}

When PerfRadar is disabled, TracedSubtree is a transparent pass-through with zero overhead.


Configuration reference #

Field Type Default Description
enabled bool kDebugMode || kProfileMode Master switch.
showOverlay bool false Whether PerfRadar.overlay() renders the badge.
jankThresholdMicros int 16667 Frames longer than this are counted as jank (~60 fps).
stallThresholdMicros int 250000 Main-thread delays longer than this are counted as stalls.
maxStallsRetained int 50 Rolling buffer depth for stall records.
maxErrorsRetained int 100 Rolling buffer depth for error records.
await PerfRadar.init(PerfRadarConfig(
  enabled: kDebugMode || kProfileMode,
  showOverlay: true,
  jankThresholdMicros: 8333,  // 120 fps threshold
  stallThresholdMicros: 100000,
));

Debug/profile-only guarantee #

The engine starts only when PerfRadarConfig.enabled is true and the build is not release (kPerfEnabled guard). In release builds every call (init, trace, traceAsync, start, overlay, frameStats, stabilitySnapshot) is a synchronous no-op returning a safe default. Nothing is conditionally compiled — no tree-shaking or build flavours required.


Features #

  • Frame timing and jank detection via SchedulerBinding timing callbacks.
  • Duplicate call detection — pass an optional dedupKey to trace/traceAsync/start, surfaced as a "N dup" count in the trace detail.
  • Stall correlation — tapping a stall opens a detail screen correlating its blocking window with the instrumented spans that overlapped it.
  • Stall watchdog — periodic heartbeat detects main-thread freezes above a configurable threshold.
  • Error captureFlutterError.onError + PlatformDispatcher.onError with rolling retention.
  • Span instrumentationtrace / traceAsync / startSpanHandle backed by radar_trace histograms and Zone-based async nesting.
  • TracedSubtree — zero-overhead rebuild counter for any widget subtree.
  • PerfRadarScreen — self-contained dark-theme dashboard with frame, span, stability, and rebuild panels.
  • Draggable overlay badge — live frame/jank indicator without leaving the running app.
  • Zero-throw contract — the engine never throws into the host app.

Package Purpose
radar_trace The underlying span/histogram engine used by this package.
radar Umbrella: one import for both flutter_leak_radar + flutter_perf_radar.
flutter_leak_radar On-device memory leak detector — heap growth, precise retention, overlay.

License #

MIT — see LICENSE.

0
likes
160
points
153
downloads

Documentation

API reference

Publisher

verified publishertp9imka.dev

Weekly Downloads

On-device performance and stability tracer for Flutter. Frame timing, startup, error capture, and main-thread stall detection in debug/profile; complete no-op in release.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#performance #tracing #observability #debugging #flutter

License

MIT (license)

Dependencies

flutter, meta, radar_trace, radar_ui

More

Packages that depend on flutter_perf_radar