diff --git a/CHANGELOG.md b/CHANGELOG.md index f343a478..d66be24c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 58a83539..b6e8cf55 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/README.md b/README.md index 16029acb..d56237e1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/doc/release_notes/1.0.0.md b/doc/release_notes/1.0.0.md index 04c93d7c..639dc89f 100644 --- a/doc/release_notes/1.0.0.md +++ b/doc/release_notes/1.0.0.md @@ -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 diff --git a/lib/sleuth.dart b/lib/sleuth.dart index cf0725e5..82ae301f 100644 --- a/lib/sleuth.dart +++ b/lib/sleuth.dart @@ -275,7 +275,8 @@ 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 @@ -283,20 +284,24 @@ class Sleuth { /// 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