Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ list every change.
the part of a widget that shows. A list builds rows past its edge, so a
card's outline could start on a hidden row and count rows the screen did
not show. A flipped or scaled widget is outlined where it shows.
- `Sleuth.track` adds nothing to a release build. Its early `return child`
left about 0.6 MB of Flutter framework code in release apps on Android
and iOS, although none of it ran.

## 0.38.0

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ test/
- Issues use `IssueConfidence`: confirmed (directly observed), likely (runtime + structural), possible (structural only).
- Fix hints use `FixHintBuilder` (in `utils/fix_hint_builder.dart`); never hardcode fixHint strings in detectors.
- All public API goes through `lib/sleuth.dart` barrel file.
- Package is completely disabled in release mode (`kReleaseMode` guard).
- Package is completely disabled in release mode (`kReleaseMode` guard). `Sleuth.track` keeps its overlay path inside `if (!kReleaseMode) { ... }`: after an early `if (kReleaseMode) return child;` the AOT compiler kept the Flutter framework code the overlay uses (~0.6 MB in release apps). The other `Sleuth` statics reach the package only through the controller `track` creates, so their early returns cost nothing.

## Current state

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void main() => runApp(Sleuth.track(child: MyApp()));
flutter run --profile --no-dds
```

Tap the trigger button to open the dashboard. The overlay appears in debug and profile mode, and release builds disable it completely. Sleuth requires Flutter 3.32 or later (Dart 3.8 or later). `event_loop_stall` needs Flutter 3.35 or later, where the VM reports timers that fire late; on older versions it stays silent. Profile mode gives accurate timing, and `--no-dds` lets Sleuth connect to the VM service, which its memory, CPU and time-share detectors need (see [Reaching full mode](#reaching-full-mode)).
Tap the trigger button to open the dashboard. The overlay appears in debug and profile mode. In release builds the compiler drops Sleuth's code, so an app wrapped in `Sleuth.track` is the same size as one without it. A `SleuthConfig` that your code builds stays in, and one with an AI adapter and a custom detector adds 64 KB. Sleuth requires Flutter 3.32 or later (Dart 3.8 or later). `event_loop_stall` needs Flutter 3.35 or later, where the VM reports timers that fire late; on older versions it stays silent. Profile mode gives accurate timing, and `--no-dds` lets Sleuth connect to the VM service, which its memory, CPU and time-share detectors need (see [Reaching full mode](#reaching-full-mode)).

## What Sleuth finds

Expand Down
8 changes: 8 additions & 0 deletions doc/release_notes/1.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ The full list of changes in sleuth 1.0.0. [CHANGELOG.md](../../CHANGELOG.md) has
an `@experimental` declaration as minor, but removing an
`@experimental` member of `Sleuth` or another class that is not
experimental as breaking.
- `Sleuth.track` adds nothing to a release build; its overlay code now
sits inside `if (!kReleaseMode)`. With the early
`if (kReleaseMode) return child;` the release compiler dropped Sleuth's
own code but kept the Flutter framework code the overlay uses, although
none of it ran. On the stock counter app that was 590 KB of Dart code on
Android (arm64) and 677 KB on iOS. Now the app's Dart code is the same
size with `Sleuth.track` as without it. Building a `SleuthConfig` with an
AI adapter and a custom detector, which happens in app code, adds 64 KB.

## Issue data and exports

Expand Down
35 changes: 20 additions & 15 deletions lib/sleuth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,28 +275,33 @@ class Sleuth {

/// Wrap your app with the performance overlay.
///
/// In release mode, this returns [child] unchanged (zero cost).
/// In release mode, this returns [child] unchanged and the overlay code
/// is left out of the build.
/// In debug/profile mode, adds the overlay with all 21 detectors.
///
/// Optionally pass [config] to customize thresholds, enable/disable
/// specific detectors, or set a custom [SleuthConfig.theme].
/// When no theme is provided, the overlay auto-selects dark or light
/// based on the system brightness.
static Widget track({required Widget child, SleuthConfig? config}) {
// Complete no-op in release mode
if (kReleaseMode) return child;

// Capture Dart entry timestamp if the app skipped explicit [init];
// init runs once. Detectors that compare timeline timestamps against
// app start (e.g. shader-warmup attribution) read
// `StartupTracker.dartEntryMonotonicUs`, which would stay null on
// `runApp(Sleuth.track(...))` without this call.
init();

final controller = SleuthController(config: config);
ActiveController.current = controller;

return SleuthOverlay(controller: controller, child: child);
// Keep the overlay path inside this branch. After an early
// `if (kReleaseMode) return child;` the release compiler still kept
// the Flutter framework code the overlay uses, about 0.6 MB; a branch
// whose condition is a constant false is dropped whole.
if (!kReleaseMode) {
// Capture Dart entry timestamp if the app skipped explicit [init];
// init runs once. Detectors that compare timeline timestamps against
// app start (e.g. shader-warmup attribution) read
// `StartupTracker.dartEntryMonotonicUs`, which would stay null on
// `runApp(Sleuth.track(...))` without this call.
init();

final controller = SleuthController(config: config);
ActiveController.current = controller;

return SleuthOverlay(controller: controller, child: child);
}
return child;
}

/// Emits a `sleuth.scenario.begin` instant trace event for profile-mode
Expand Down
Loading