├── .github ├── ISSUE_TEMPLATE │ ├── leak_tracker.md │ ├── leak_tracker_flutter_testing.md │ ├── leak_tracker_testing.md │ └── memory_usage.md ├── dependabot.yaml ├── labeler.yml └── workflows │ ├── ci.yaml │ └── pull_request_label.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── AUTHORS ├── CODEOWNERS ├── LICENSE ├── README.md ├── doc ├── BASELINE.md ├── USAGE.md └── leak_tracking │ ├── CONCEPTS.md │ ├── CONTRIBUTING.md │ ├── DETECT.md │ ├── MOTIVATION.md │ ├── OVERVIEW.md │ ├── TROUBLESHOOT.md │ └── images │ ├── leak.png │ └── rename.png ├── examples ├── autosnapshotting │ ├── .gitignore │ ├── README.md │ ├── analysis_options.yaml │ ├── integration_test │ │ └── app_test.dart │ ├── lib │ │ └── main.dart │ └── pubspec.yaml └── leak_tracking │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ └── main.dart │ └── pubspec.yaml ├── pkgs ├── leak_tracker │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── main.dart │ │ └── pubspec.yaml │ ├── lib │ │ ├── DEPENDENCIES.md │ │ ├── leak_tracker.dart │ │ └── src │ │ │ ├── DEPENDENCIES.md │ │ │ ├── leak_tracking │ │ │ ├── DEPENDENCIES.md │ │ │ ├── _baseliner.dart │ │ │ ├── _leak_filter.dart │ │ │ ├── _leak_reporter.dart │ │ │ ├── _leak_tracker.dart │ │ │ ├── _object_record.dart │ │ │ ├── _object_record_set.dart │ │ │ ├── _object_records.dart │ │ │ ├── _object_tracker.dart │ │ │ ├── helpers.dart │ │ │ ├── leak_tracking.dart │ │ │ └── primitives │ │ │ │ ├── README.md │ │ │ │ ├── _dispatcher.dart │ │ │ │ ├── _finalizer.dart │ │ │ │ ├── _gc_counter.dart │ │ │ │ ├── _print_bytes.dart │ │ │ │ ├── _retaining_path │ │ │ │ ├── DEPENDENCIES.md │ │ │ │ ├── _connection.dart │ │ │ │ ├── _retaining_path.dart │ │ │ │ ├── _retaining_path_isolate.dart │ │ │ │ └── _retaining_path_web.dart │ │ │ │ ├── _test_helper_detector.dart │ │ │ │ └── model.dart │ │ │ └── shared │ │ │ ├── DEPENDENCIES.md │ │ │ ├── _formatting.dart │ │ │ ├── _primitives.dart │ │ │ ├── _util.dart │ │ │ └── shared_model.dart │ ├── pubspec.yaml │ └── test │ │ ├── test_infra │ │ ├── data │ │ │ └── dart_classes.dart │ │ ├── mocks │ │ │ └── mock_object_tracker.dart │ │ └── utils.dart │ │ └── tests │ │ ├── leak_tracking │ │ ├── _gc_counter_test.dart │ │ ├── _leak_checker_test.dart │ │ ├── _leak_filter_test.dart │ │ ├── _object_record_set_test.dart │ │ ├── _object_tracker_test.dart │ │ ├── _primitives │ │ │ └── _test_helper_detector_test.dart │ │ ├── dart_developer_test.dart │ │ ├── helpers_test.dart │ │ ├── model_test.dart │ │ ├── orchestration_test.dart │ │ └── retaining_path │ │ │ └── _retaining_path_test.dart │ │ └── shared │ │ └── _primitives_test.dart ├── leak_tracker_flutter_testing │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── DEPENDENCIES.md │ │ ├── leak_tracker_flutter_testing.dart │ │ └── src │ │ │ ├── matchers.dart │ │ │ ├── testing.dart │ │ │ └── testing_for_testing │ │ │ ├── README.md │ │ │ ├── leaking_classes.dart │ │ │ ├── test_case.dart │ │ │ └── test_settings.dart │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ └── test │ │ ├── test_infra │ │ ├── event_tracker.dart │ │ ├── memory_leak_tests.dart │ │ └── test_helpers.dart │ │ └── tests │ │ ├── README.md │ │ ├── _dispatcher_test.dart │ │ ├── _formatting_test.dart │ │ ├── end_to_end │ │ ├── _retaining_path_test.dart │ │ ├── leak_detection_test.dart │ │ └── test_helpers_test.dart │ │ ├── leak_tracking_for_tests_test.dart │ │ ├── matchers_test.dart │ │ └── testing_test.dart ├── leak_tracker_testing │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── DEPENDENCIES.md │ │ ├── leak_tracker_testing.dart │ │ └── src │ │ │ ├── DEPENDENCIES.md │ │ │ ├── leak_testing.dart │ │ │ └── matchers.dart │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ └── test │ │ ├── end_to_end_test.dart │ │ ├── leak_testing_test.dart │ │ └── matchers_test.dart ├── leak_tracker_web_tests │ ├── README.md │ ├── analysis_options.yaml │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ └── test │ │ ├── leak_detection_test.dart │ │ └── smoke_test.dart └── memory_usage │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ ├── README.md │ ├── analysis_options.yaml │ ├── main.dart │ └── pubspec.yaml │ ├── lib │ ├── DEPENDENCIES.md │ ├── memory_usage.dart │ └── src │ │ └── auto_snapshotting │ │ ├── DEPENDENCIES.md │ │ ├── _snapshot.dart │ │ ├── _usage_event.dart │ │ ├── _util.dart │ │ ├── auto_snapshotting.dart │ │ └── model.dart │ ├── pubspec.yaml │ └── test │ ├── _snapshot_test.dart │ └── model_test.dart └── tool ├── analyze.sh ├── diagrams.sh ├── lt_publish.sh └── pub_get.sh /.github/ISSUE_TEMPLATE/leak_tracker.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:leak_tracker" 3 | about: "Create a bug or file a feature request against package:leak_tracker." 4 | labels: "package:leak_tracker" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/leak_tracker_flutter_testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:leak_tracker_flutter_testing" 3 | about: "Create a bug or file a feature request against package:leak_tracker_flutter_testing." 4 | labels: "package:leak_tracker_flutter_testing" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/leak_tracker_testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:leak_tracker_testing" 3 | about: "Create a bug or file a feature request against package:leak_tracker_testing." 4 | labels: "package:leak_tracker_testing" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/memory_usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:memory_usage" 3 | about: "Create a bug or file a feature request against package:memory_usage." 4 | labels: "package:memory_usage" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | version: 2 3 | enable-beta-ecosystems: true 4 | 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | labels: 11 | - autosubmit 12 | 13 | - package-ecosystem: "pub" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | # TODO: Remove this if / when the default changes (dependabot/dependabot-core/issues/4979) 18 | versioning-strategy: increase-if-necessary 19 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Configuration for .github/workflows/pull_request_label.yml. 2 | 3 | 'type-infra': 4 | - changed-files: 5 | - any-glob-to-any-file: '.github/**' 6 | 7 | 'package:leak_tracker': 8 | - changed-files: 9 | - any-glob-to-any-file: 'pkgs/leak_tracker/**' 10 | 11 | 'package:leak_tracker_flutter_testing': 12 | - changed-files: 13 | - any-glob-to-any-file: 'pkgs/leak_tracker_flutter_testing/**' 14 | 15 | 'package:leak_tracker_testing': 16 | - changed-files: 17 | - any-glob-to-any-file: 'pkgs/leak_tracker_testing/**' 18 | 19 | 'package:memory_usage': 20 | - changed-files: 21 | - any-glob-to-any-file: 'pkgs/memory_usage/**' 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # TODO(polina-c): configure auto-update for diagrams 4 | # https://github.com/dart-lang/leak_tracker/issues/104 5 | 6 | on: 7 | schedule: 8 | # “At 00:00 (UTC) on Sunday.” 9 | - cron: '0 0 * * 0' 10 | push: 11 | branches: [ main ] 12 | pull_request: 13 | branches: [ main ] 14 | 15 | jobs: 16 | ci: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: clone the repo 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 22 | 23 | - name: install Flutter sdk 24 | uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 25 | with: 26 | channel: 'master' 27 | 28 | - name: version 29 | run: flutter --version 30 | 31 | - name: analyze 32 | run: sh ./tool/analyze.sh 33 | 34 | # unit tests: 35 | 36 | - name: test leak_tracker 37 | run: dart test 38 | working-directory: pkgs/leak_tracker 39 | 40 | - name: test leak_tracker_testing 41 | run: dart test 42 | working-directory: pkgs/leak_tracker_testing 43 | 44 | - name: test leak_tracker_flutter_testing 45 | run: flutter test --enable-vmservice 46 | working-directory: pkgs/leak_tracker_flutter_testing 47 | 48 | - name: test leak_tracker_web_tests 49 | run: flutter test --platform chrome 50 | working-directory: pkgs/leak_tracker_web_tests 51 | 52 | - name: test memory_usage 53 | run: dart test 54 | working-directory: pkgs/memory_usage 55 | 56 | # integration tests: 57 | 58 | - name: integration test examples/autosnapshotting 59 | run: flutter test integration_test/app_test.dart -d flutter-tester 60 | working-directory: examples/autosnapshotting 61 | 62 | # cycles: 63 | 64 | - name: cycles in leak_tracker 65 | run: dart run layerlens --fail-on-cycles 66 | working-directory: pkgs/leak_tracker 67 | 68 | - name: cycles in leak_tracker_testing 69 | run: dart run layerlens --fail-on-cycles 70 | working-directory: pkgs/leak_tracker_testing 71 | 72 | - name: cycles in leak_tracker_flutter_testing 73 | run: dart run layerlens --fail-on-cycles 74 | working-directory: pkgs/leak_tracker_flutter_testing 75 | 76 | - name: cycles in memory_usage 77 | run: dart run layerlens --fail-on-cycles 78 | working-directory: pkgs/memory_usage 79 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | # This workflow applies labels to pull requests based on the paths that are 2 | # modified in the pull request. 3 | # 4 | # Edit `.github/labeler.yml` to configure labels. For more information, see 5 | # https://github.com/actions/labeler. 6 | 7 | name: Pull Request Labeler 8 | permissions: read-all 9 | 10 | on: 11 | pull_request_target 12 | 13 | jobs: 14 | label: 15 | permissions: 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 20 | with: 21 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 22 | sync-labels: true 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | .flutter-plugins 8 | .flutter-plugins-dependencies 9 | 10 | # Omit committing pubspec.lock for library packages; see 11 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 12 | pubspec.lock 13 | 14 | **/macos/**/ephemeral/** 15 | GeneratedPluginRegistrant.swift 16 | 17 | # Platforms 18 | examples/**/ios/ 19 | examples/**/web/ 20 | examples/**/windows/ 21 | examples/**/macos/ 22 | examples/**/.metadata 23 | 24 | # OS 25 | .DS_Store 26 | logs/ 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", //https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "example", 9 | "cwd": "pkgs/leak_tracker/example", 10 | "request": "launch", 11 | "type": "dart", 12 | "program": "main.dart", 13 | }, 14 | { 15 | "name": "pub_get", 16 | "request": "launch", 17 | "type": "node-terminal", 18 | "command": "sh tool/pub_get.sh", 19 | }, 20 | { 21 | "name": "minimal_flutter", 22 | "cwd": "examples/minimal_flutter", 23 | "request": "launch", 24 | "type": "dart", 25 | "program": "lib/main.dart", 26 | }, 27 | { 28 | "name": "autosnapshotting", 29 | "cwd": "examples/autosnapshotting", 30 | "request": "launch", 31 | "type": "dart", 32 | "program": "lib/main.dart", 33 | }, 34 | { 35 | "name": "current test file", 36 | "request": "launch", 37 | "type": "dart", 38 | }, 39 | { 40 | "name": "web tests", 41 | "cwd": "pkgs/web_tests", 42 | "request": "launch", 43 | "type": "dart", 44 | "program": "test/", 45 | "args": [ 46 | "--platform", 47 | "chrome", 48 | ], 49 | }, 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Set current working directory to pkgs/leak_tracker. 3 | "terminal.integrated.cwd": "pkgs/leak_tracker", 4 | "cSpell.words": [ 5 | "autosnapshotting", 6 | "baselining", 7 | "callstack", 8 | "cupertino", 9 | "finalizer", 10 | "gced", 11 | "layerlens", 12 | "pkgs", 13 | "polina", 14 | "polinach", 15 | "pubspec", 16 | "snapshotting", 17 | "sublist", 18 | "unawaited", 19 | "Upvote" 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See 2 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 3 | 4 | * @polina-c @CoderDake 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![CI](https://github.com/dart-lang/leak_tracker/actions/workflows/ci.yaml/badge.svg)](https://github.com/dart-lang/leak_tracker/actions/workflows/ci.yaml) 3 | 4 | # Memory leak_tracker 5 | 6 | This is a framework for detecting and troubleshooting memory issues in Dart and Flutter applications. 7 | 8 | ## Packages 9 | 10 | | Package | Description | Version | 11 | | --- | --- | --- | 12 | | [leak_tracker](pkgs/leak_tracker/) | (work in progress, used by flutter_test) A framework for memory leak tracking for Dart and Flutter applications. | [![pub package](https://img.shields.io/pub/v/leak_tracker.svg)](https://pub.dev/packages/leak_tracker) | 13 | | [leak_tracker_testing](pkgs/leak_tracker_testing/) | (work in progress, used by flutter_test) Leak tracking code intended for usage in tests. | [![pub package](https://img.shields.io/pub/v/leak_tracker_testing.svg)](https://pub.dev/packages/leak_tracker_testing) | 14 | | [leak_tracker_flutter_testing](pkgs/leak_tracker_flutter_testing/) | An internal package to test leak tracking with Flutter. | [![pub package](https://img.shields.io/pub/v/leak_tracker_flutter_testing.svg)](https://pub.dev/packages/leak_tracker_flutter_testing) | 15 | | [memory_usage](pkgs/memory_usage/) | (experimental) A framework for memory usage tracking and snapshotting. | [![pub package](https://img.shields.io/pub/v/memory_usage.svg)](https://pub.dev/packages/memory_usage) | 16 | 17 | ## Guidance 18 | 19 | Ready for use: 20 | 21 | - [Memory usage](doc/USAGE.md) 22 | 23 | Under construction: 24 | 25 | - [Memory leak tracking](doc/leak_tracking/OVERVIEW.md) 26 | - [Memory baselining](doc/BASELINE.md) 27 | 28 | ## Contributing 29 | 30 | For general contributing information, see Dart-wide [CONTRIBUTING.md](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md). 31 | 32 | For package-specific contributing guidance see: 33 | 34 | * [Leak tracking](doc/leak_tracking/CONTRIBUTING.md) 35 | * [Memory usage](doc/USAGE.md#contributing) 36 | -------------------------------------------------------------------------------- /doc/BASELINE.md: -------------------------------------------------------------------------------- 1 | Coming soon! 2 | 3 | The text below is under construction. 4 | 5 | # Memory baselining 6 | 7 | ## What is it? 8 | 9 | Memory baselining helps you to measure if your code change impacted memory footprint of a feature. 10 | 11 | Please, note, that the obtained numbers are not showing real values in released application, 12 | because. The numbers can be used only as relative measure, when the only change between runs 13 | is the code. 14 | All other parameters, like version of Dart/Flutter SDK, target platform, build mode, hardware, 15 | should stay the same. 16 | 17 | ## How to use it? 18 | 19 | To take baseline of memory footprint for your `testWidgetsWithLeakTracking`, pass baselining configuration to the test. It is recommended 20 | to run the test more than once to stabilize the numbers. 21 | After first execution copy the code in the output as parameter of `MemoryBaselining`. 22 | 23 | Then make your change, run the test again and see how the change affected memory footprint. 24 | 25 | Code example: 26 | 27 | ```dart 28 | for (var i in Iterable.generate(1000)) { 29 | testWidgetsWithLeakTracking( 30 | 'baselining with multiple runs', 31 | (widgetTester) async { 32 | ... 33 | }, 34 | experimentalLeakTesting: LeakTesting.settings.copyWith( 35 | baselining: MemoryBaselining(), 36 | ), 37 | ); 38 | } 39 | 40 | ``` 41 | 42 | The output will be like this: 43 | 44 | ``` 45 | initialValue: 136.2 MB - 138.0 MB = -1.9 MB (-1.35%) 46 | deltaAvg: 7.7 MB - 7.7 MB = 28 KB (0.35%) 47 | deltaMax: 13.0 MB - 13.0 MB = 48 KB (0.36%) 48 | absAvg: 143.8 MB - 145.7 MB = -1.8 MB (-1.26%) 49 | absMax: 149.2 MB - 151.0 MB = -1.8 MB (-1.20%) 50 | samples: 249 - 249 = 0 51 | 52 | To set as new baseline, set parameter of MemoryBaselining: 53 | baseline: MemoryBaseline( 54 | rss: ValueSampler(initialValue: 142770176, deltaAvg: 8089353.253012049, deltaMax: 13680640, absAvg: 150827171.84000006, absMax: 156450816, samples: 249,), 55 | ) 56 | ``` 57 | 58 | ## Limitations 59 | 60 | ### For instrumented classes only 61 | 62 | Baselining in `leak_tracker` only works, if the measured code deals with 63 | [instrumented classes](DETECT.md#limitations), 64 | because samples are taken at the moments when 65 | objects dispatch their creation or disposal. 66 | 67 | Flutter Framework contains a lot of instrumented classes, 68 | so baselining normally works well for 69 | Flutter tests. 70 | 71 | ### Platform 72 | 73 | Baselining works for dart vm based platforms, that means it does not 74 | work for web. 75 | 76 | ## Regression testing 77 | 78 | If you want your tests to fail in case of significant deviation from baseline, 79 | please, upvote and comment the issue: https://github.com/dart-lang/leak_tracker/issues/120. 80 | 81 | ## Other than RSS values 82 | 83 | If you want to measure other than RSS values, 84 | please, upvote and comment the issue: https://github.com/dart-lang/sdk/issues/53134 85 | -------------------------------------------------------------------------------- /doc/USAGE.md: -------------------------------------------------------------------------------- 1 | # Memory usage tracking and auto-snapshotting 2 | 3 | This page describes how to configure memory usage tracking. 4 | See other information on memory debugging [here](../README.md). 5 | 6 | Dart and Flutter applications can be configured to automatically 7 | trigger memory usage events and, in case of memory overuse, to save 8 | memory heap snapshots to hard drive. 9 | The snapshots can be later analyzed in DevTools. 10 | 11 | https://user-images.githubusercontent.com/12115586/234953319-6f864d25-9854-4126-b4d6-8e114b9045ff.mov 12 | 13 | ## Configure usage tracking 14 | 15 | Use the function `trackMemoryUsage` to configure usage events and auto-snapshotting. 16 | 17 | See [example](../examples/autosnapshotting/). 18 | 19 | We recommend to have auto-snapshotting off by default, with possibility 20 | to enable it via command line arguments in case of reported memory issues. 21 | 22 | ## Analyze snapshots 23 | 24 | Use [CLI](https://github.com/dart-lang/sdk/tree/main/runtime/tools/heapsnapshot#cli-usage) to analyze the collected snapshots. 25 | 26 | Upvote [the issue](https://github.com/dart-lang/leak_tracker/issues/125) to enable graphical snapshot analysis with DevTools. 27 | 28 | ## Auto-snapshotting limitations 29 | 30 | ### By platform 31 | 32 | Usage tracking does not work for web platform. 33 | 34 | ### By build mode 35 | 36 | While usage tracking events are available for all modes, auto-snapshotting 37 | is on or off depending on the build mode: 38 | 39 | * **Enabled for:** Flutter debug and profile modes (equivalent to Dart debug and release modes). 40 | * **Disabled for:** Flutter release mode (equivalent to Dart product mode). 41 | 42 | See [Dart build modes](https://github.com/dart-lang/site-www/issues/4436) 43 | or [Flutter build modes](https://docs.flutter.dev/testing/build-modes). 44 | 45 | ## Contributing 46 | 47 | For general contributing information, see Dart-wide [CONTRIBUTING.md](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md). 48 | 49 | ### How to roll the latest version of `memory_usage` to the Dart SDK repo 50 | 51 | To upgrade Dart SDK with new version of `memory_usage` update leak_tracker commit 52 | hash for leak_tracker_rev in [Dart SDK DEPS](https://github.com/dart-lang/sdk/blob/main/DEPS). 53 | -------------------------------------------------------------------------------- /doc/leak_tracking/CONCEPTS.md: -------------------------------------------------------------------------------- 1 | Coming soon! See https://github.com/flutter/devtools/issues/3951. 2 | 3 | The text below is under construction. 4 | 5 | # Understand Memory Leak Tracking Concepts 6 | 7 | This page describes leak tracking concepts. 8 | Read more about leak tracking in [overview](OVERVIEW.md). 9 | 10 | Before reading about leak tracking concepts, understand [Dart memory concepts](https://docs.flutter.dev/development/tools/devtools/memory#basic-memory-concepts). 11 | 12 | ## Object types 13 | 14 | ### Instrumented disposables 15 | 16 | An instrumented disposable is a [disposable](https://docs.flutter.dev/tools/devtools/memory#disposable-object) 17 | class whose creation and disposal are instrumented for 18 | tracking by tools like leak_tracker. The instrumentation is done by dispatching events directly 19 | to the `LeakTracking` class, or by dispatching events to the `FlutterMemoryAllocations` class, 20 | which is listened to by `LeakTracking`. 21 | 22 | ### Package disposables 23 | 24 | All [disposable](https://docs.flutter.dev/tools/devtools/memory#disposable-object) objects defined in a package. 25 | 26 | ## Addressed leak types 27 | 28 | The leak_tracker catches leaks related to the timing of object disposal and garbage collection (GC). When memory is being managed properly, an object's disposal and GC should occur in quick succession. After disposal, an object should be garbage collected during the next GC cycle. The tool uses this assumption to catch cases that do not follow this pattern. 29 | 30 | By monitoring disposal and GC events, the tool detects 31 | the following types of leaks: 32 | 33 | ### Not disposed, but GCed (not-disposed) 34 | 35 | - **Definition**: a disposable object was GCed, 36 | without being disposed first. This means that the object's disposable content 37 | is using memory after the object is no longer needed. 38 | 39 | - **Fix**: invoke `dispose()` for the object to free up the memory. 40 | 41 | ### Disposed, but not GCed (not-GCed) 42 | 43 | - **Definition**: an object was disposed, 44 | but not GCed after certain number of GC events. This means that 45 | a reference to the object is preventing it from being 46 | garbage collected after it's no longer needed. 47 | 48 | - **Fix**: To fix the leak, assign all reachable references 49 | of the object to null after disposal: 50 | 51 | ```dart 52 | myField.dispose(); 53 | myField = null; 54 | ``` 55 | 56 | ### Disposed and GCed late (GCed-late) 57 | 58 | - **Definition**: an object was disposed and then GCed, 59 | but GC happened later than expected. This means the retaining path was 60 | holding the object in memory for some period, but then disappeared. 61 | 62 | - **Fix**: the same as for **not-GCed** 63 | 64 | ### Disposed, but not GCed, without path (not-GCed-without-path) 65 | 66 | - **Definition**: an object 67 | was disposed and not GCed when expected, but retaining path 68 | is not detected; that means that the object will be most likely GCed in 69 | the next GC cycle, 70 | and the leak will convert to [GCed-late](#gced-late) leak. 71 | 72 | - **Fix**: please, 73 | [create an issue](https://github.com/dart-lang/leak_tracker/issues) 74 | if you see this type of leak, as it means 75 | something is wrong with the tool. 76 | 77 | ## Culprits and victims 78 | 79 | If you have a set of not-GCed objects, some of them (victims) 80 | might not be GC-ed because they are held by others (culprits). 81 | Normally, to fix the leaks, you need to only fix the culprits. 82 | 83 | **Victim**: a leaked object, for which the tool could find another 84 | leaked object that, if fixed, would also fix the first leak. 85 | 86 | **Culprit**: a leaked object that is not detected to be the victim 87 | of another object. 88 | 89 | The tool detects which leaked objects are culprits, so you know where to focus. 90 | 91 | For example, out of four not-GCed leaks on the following diagram, 92 | only one is the culprit, because, when the object is fixed 93 | and GCed, the victims it referenced will be also GCed: 94 | 95 | Screenshot 2023-01-26 at 4 31 54 PM 96 | -------------------------------------------------------------------------------- /doc/leak_tracking/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to leak tracker. 2 | 3 | For general contributing information, see Dart-wide [CONTRIBUTING.md](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md). 4 | 5 | ## Roll the latest version of `leak_tracker` to Flutter 6 | 7 | To upgrade version of leak tracking packages used by Flutter and thus used by 8 | all applications that depend on Flutter: 9 | 10 | 1. Publish new version of the packages by running `sh tool/lt_publish.sh`. 11 | 12 | 2. If the changes affect APIs used by Flutter, 13 | ask a googler to refresh the packages with copybara in G3. 14 | 15 | 3. Upgrade [Flutter](https://github.com/flutter/flutter): 16 | 17 | 1. If `leak_tracker*` is pinned in 18 | [update_packages_pins.dart](https://github.com/flutter/flutter/blob/main/packages/flutter_tools/lib/src/update_packages_pins.dart), 19 | update the versions. 20 | 2. In `packages/flutter` run `../../bin/flutter update-packages --force-upgrade` 21 | 22 | If upgrade for all packages causes complicated failure of bots, cherry pick upgrade for 23 | just leak tracker: 24 | 25 | ``` 26 | ../../bin/flutter update-packages --cherry-pick-package leak_tracker --cherry-pick-version 27 | ../../bin/flutter update-packages --cherry-pick-package leak_tracker_flutter_testing --cherry-pick-version 28 | ``` 29 | 30 | ## Regenerate DEPENDENCIES.md 31 | 32 | To regenerate [diagrams](https://pub.dev/packages/layerlens), run in the root of packages: 33 | 34 | ```shell 35 | sh tool/diagrams.sh 36 | ``` 37 | 38 | ## Use different `leak_tracker` 39 | 40 | When you reference `leak_tracker` from your application, version should be `any`, because 41 | the version is pinned by Flutter. 42 | 43 | If you want to use a different version of `leak_tracker` in your Flutter application, 44 | you can reference a local renamed leak tracker package: 45 | 46 | 1. Clone it: `git clone git@github.com:dart-lang/leak_tracker.git` 47 | 48 | 2. Make global replacements: 49 | 50 | - In all '*.dart' files: replace 'package:leak_tracker' with 'package:new_leak_tracker' 51 | 52 | - In all files 'pubspec.yaml, pubspec_overrides.yaml': replace ' leak_tracker' with ' new_leak_tracker' 53 | 54 | ![replace](images/rename.png "Rename leak_tracker") 55 | 56 | 3. Follow steps in [DETECT](./DETECT.md) to enable leak tracking, referencing 57 | the local `new_leak_tracker*`, instead of what is instructed: 58 | 59 | ``` 60 | new_leak_tracker...: 61 | path: 62 | ``` 63 | -------------------------------------------------------------------------------- /doc/leak_tracking/MOTIVATION.md: -------------------------------------------------------------------------------- 1 | 2 | Coming soon! See https://github.com/flutter/devtools/issues/3951. 3 | 4 | The text below is under construction. 5 | 6 | # Motivation for leak tracking 7 | 8 | This page describes motivation behind memory leak tracking. 9 | Read more about leak tracking in [overview](OVERVIEW.md). 10 | 11 | ## Why do disposables exist? 12 | 13 | In most cases, an object's lifecycle is managed well by the Dart garbage collector. 14 | 15 | However, some objects (memory-risky objects) require special care to avoid the risk of memory leaks: 16 | 1. Objects that allocate significant memory for their members 17 | 2. Objects that reference a significant number of other objects, designed to be short living (like the entire widget tree), 18 | and therefore prevent them from being GCed in time. 19 | 20 | ![leak](images/leak.png "Leak") 21 | 22 | Such objects usually have a method `dispose`, which has two purposes: 23 | 24 | 1. Releases the resources, to make sure they do not stay longer than needed. 25 | 2. Explicitly marks the object as not needed, to make lifecycle management easier. For example: 26 | a. Some objects restrict invocation of some methods after disposal. 27 | b. It is expected that objects should be available for GC after disposal, so owners of the objects should null the references after invocation of `dispose`. 28 | 29 | ## Memory efficiency of applications 30 | 31 | To ensure **memory efficiency** of applications, engineers should watch that: 32 | 1. All disposables are disposed when they are not needed any more. 33 | 2. Large objects and objects that reference large sets of other objects are not reachable after usage. 34 | 3. Objects allocate a reasonable amount of memory necessary to perform their tasks. 35 | 36 | Engineers want their applications to be memory efficient, but they want to minimize time and mental effort spent worrying about the items above. 37 | 38 | leak_tracker fully automates item #1, and almost* fully automates item #2. 39 | 40 | > [!NOTE] 41 | > *If a large set of objects is held from GC longer than needed, some of them are likely to be disposables, and thus leaks will be detected by the leak_tracker. This is not always true, though; in rare cases, a large set of objects is held from GC, but the set does not contain any disposables tracked by leak_tracker. 42 | 43 | As a result, `leak_tracker` increases confidence and reduces engineering effort related to memory efficiency. 44 | 45 | Item #3 is not covered by `leak_tracker`. There are no known tools to automate it so far. All known methods are manual: 46 | 1. Analyzing the consumed memory using DevTools snapshotting and diffing 47 | 2. [Memory baselining](../BASELINE.md) in regression tests 48 | 49 | ## How large can memory leaks be? 50 | 51 | How large can a memory leak be when disposal is forgotten or when an object is referenced after disposal? 52 | 53 | The cases below are selected from the GitHub issue history and illustrate three types of memory issues: 54 | 55 | * Out of memory crash 56 | * 100K+ leaks 57 | * Leaks with no numbers recorded, but significant enough to file, investigate and fix the issue 58 | 59 | ### Forgotten `dispose` 60 | 61 | 1. VideoController caused crash: https://github.com/flutter/flutter/issues/72643 62 | 2. Canvas size is growing infinitely: https://github.com/flutter/flutter/issues/58437 63 | 3. WebGL caused 2GB+ Leak: https://github.com/flutter/flutter/issues/52485 64 | 4. Out of memory crash in web app: https://github.com/dart-lang/leak_tracker/issues/258 65 | 66 | ### Disposed, but notGCed 67 | 68 | 1. Disposed but not GCed Route is leaking: https://github.com/flutter/flutter/issues/88073. 69 | (A route may reference the entire page widget tree.) 70 | 71 | 2. A Flutter customer building a large application found the application was leaking over 300KB at startup. Many of the leaked objects would be detected by this package. 72 | 73 | 74 | ### Fixed `dispose` 75 | 76 | Issues here are fixed by updating ‘dispose’, not invoking it. 77 | That means not invoked ‘dispose’ would cause a significant leak. 78 | 79 | 1. `Vertices` causes app crash: https://github.com/flutter/flutter/issues/54762 80 | 2. `VideoPlayerController` causes leak 25MB: https://github.com/flutter/flutter/issues/86477 81 | 3. `AnimationController` causes significant leak: https://github.com/flutter/flutter/issues/84730 82 | 4. `ImageShader` causes significant leak: https://github.com/flutter/flutter/issues/82832 83 | 5. Google map causes app crash: https://github.com/flutter/flutter/issues/35243 84 | 6. Images in `GridView` causes crash: https://github.com/flutter/flutter/issues/19558 85 | 7. `CanvasImage` causes significant leak: https://github.com/flutter/flutter/issues/57746 86 | -------------------------------------------------------------------------------- /doc/leak_tracking/OVERVIEW.md: -------------------------------------------------------------------------------- 1 | # Leak tracking overview 2 | 3 | Detecting memory leaks for large applications is hard ([snapshot diffing](https://nodejs.org/en/docs/guides/diagnostics/memory/using-heap-snapshot), [profiling](https://www.atatus.com/blog/how-to-identify-memory-leaks/#:~:text=doomed%20to%20fail.-,Is%20There%20a%20Way%20to%20Tell%20a%20Memory%20Leak%3F,RAM%20and%20crash%20your%20application.)). Normally, the leaks impact users, staying invisible for application teams. 4 | 5 | `leak_tracker` helps to catch memory issues much earlier by detecting not-disposed and (experimentally) not-garbage-collected objects in Flutter regression tests. 6 | 7 | ## Read more 8 | 9 | - [Concepts](CONCEPTS.md) 10 | - [Motivation](MOTIVATION.md) 11 | - [Detect memory leaks](DETECT.md) 12 | - [Troubleshoot memory leaks](TROUBLESHOOT.md) 13 | -------------------------------------------------------------------------------- /doc/leak_tracking/images/leak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-lang/leak_tracker/b3b95c04c3ce6f765ea64845f1ab33134c119c08/doc/leak_tracking/images/leak.png -------------------------------------------------------------------------------- /doc/leak_tracking/images/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-lang/leak_tracker/b3b95c04c3ce6f765ea64845f1ab33134c119c08/doc/leak_tracking/images/rename.png -------------------------------------------------------------------------------- /examples/autosnapshotting/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /examples/autosnapshotting/README.md: -------------------------------------------------------------------------------- 1 | # autosnapshotting 2 | 3 | Example of autosnapshotting usage. 4 | -------------------------------------------------------------------------------- /examples/autosnapshotting/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /examples/autosnapshotting/integration_test/app_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:autosnapshotting_example/main.dart' as app; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:integration_test/integration_test.dart'; 10 | 11 | // Prerequisite to run for macos: 12 | // flutter create --platforms=macos . 13 | 14 | // Run for macos: 15 | // flutter test integration_test/app_test.dart -d macos 16 | 17 | // Run headless: 18 | // flutter test integration_test/app_test.dart -d flutter-tester 19 | 20 | const _testDirRoot = 'test_dart_snapshots'; 21 | 22 | void main() { 23 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 24 | 25 | testWidgets('Snapshots are not taken after reaching limit', (tester) async { 26 | // Delay needed to detect memory usage is increased and take snapshot. 27 | const delayForSnapshot = Duration(seconds: 10); 28 | app.main([], snapshotDirectory: '$_testDirRoot/$pid'); 29 | await tester.pumpAndSettle(); 30 | 31 | final pageState = 32 | tester.state(find.byType(app.MyHomePage)); 33 | final config = pageState.config; 34 | final theButton = find.byTooltip('Allocate more memory'); 35 | 36 | // Take first snapshot 37 | final firstThreshold = config.autoSnapshottingConfig!.thresholdMb.mbToBytes; 38 | while (pageState.lastRss <= firstThreshold) { 39 | await tester.tap(theButton); 40 | await tester.pumpAndSettle(); 41 | } 42 | await tester.runAsync(() => Future.delayed(delayForSnapshot)); 43 | expect(pageState.snapshots.length, greaterThan(0)); 44 | 45 | // Take second threshold 46 | final secondThreshold = pageState.lastRss + 47 | config.autoSnapshottingConfig!.increaseMb!.mbToBytes; 48 | var snapshotsLength = pageState.snapshots.length; 49 | while (pageState.lastRss <= secondThreshold) { 50 | await tester.tap(theButton); 51 | await tester.pumpAndSettle(); 52 | } 53 | await tester.runAsync(() => Future.delayed(delayForSnapshot)); 54 | expect(pageState.snapshots.length, snapshotsLength + 1); 55 | 56 | // Check the directory limit is respected. 57 | while (directorySize(config.autoSnapshottingConfig!.directory) <= 58 | config.autoSnapshottingConfig!.directorySizeLimitMb.mbToBytes) { 59 | await tester.tap(theButton); 60 | await tester.pumpAndSettle(); 61 | await tester 62 | .runAsync(() => Future.delayed(const Duration(seconds: 1))); 63 | } 64 | snapshotsLength = pageState.snapshots.length; 65 | for (var _ in Iterable.generate(10)) { 66 | await tester.tap(theButton); 67 | await tester.pumpAndSettle(); 68 | } 69 | expect(pageState.snapshots, hasLength(snapshotsLength)); 70 | 71 | expect(pageState.usageEvents.length, inInclusiveRange(4, 12)); 72 | }); 73 | 74 | tearDownAll(() { 75 | Directory(_testDirRoot).deleteSync(recursive: true); 76 | }); 77 | } 78 | 79 | int directorySize(String path) => Directory(path) 80 | .listSync(recursive: true) 81 | .whereType() 82 | .map((f) => f.lengthSync()) 83 | .fold(0, (a, b) => a + b); 84 | -------------------------------------------------------------------------------- /examples/autosnapshotting/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:intl/intl.dart'; 9 | import 'package:memory_usage/memory_usage.dart'; 10 | 11 | late String _snapshotDirectory; 12 | 13 | extension SizeConversion on int { 14 | static const _bytesInMB = 1024 * 1024; 15 | int get mbToBytes => this * _bytesInMB; 16 | int get bytesToMB => (this / _bytesInMB).round(); 17 | } 18 | 19 | void main(List args, {String? snapshotDirectory}) { 20 | _snapshotDirectory = snapshotDirectory ?? 'dart_snapshots'; 21 | runApp(const MyApp()); 22 | } 23 | 24 | class MyApp extends StatelessWidget { 25 | const MyApp({super.key}); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return MaterialApp( 30 | title: 'Autosnapshotting Demo', 31 | theme: ThemeData( 32 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 33 | useMaterial3: true, 34 | ), 35 | home: const MyHomePage( 36 | title: 'Autosnapshotting Demo', 37 | ), 38 | ); 39 | } 40 | } 41 | 42 | class MyHomePage extends StatefulWidget { 43 | const MyHomePage({super.key, required this.title}); 44 | 45 | final String title; 46 | 47 | @override 48 | State createState() => MyHomePageState(); 49 | } 50 | 51 | final _allocations = >[]; 52 | 53 | class MyHomePageState extends State { 54 | final _formatter = NumberFormat('#,###,000'); 55 | final snapshots = []; 56 | final usageEvents = []; 57 | int lastRss = 0; 58 | late UsageTrackingConfig config; 59 | 60 | void _allocateMemory() { 61 | setState(() { 62 | _allocations.add(List.generate(1000000, (_) => DateTime.now())); 63 | lastRss = ProcessInfo.currentRss; 64 | }); 65 | } 66 | 67 | @override 68 | void initState() { 69 | super.initState(); 70 | 71 | config = UsageTrackingConfig( 72 | autoSnapshottingConfig: AutoSnapshottingConfig( 73 | onSnapshot: _handleSnapshotEvent, 74 | thresholdMb: 400, 75 | increaseMb: 100, 76 | directorySizeLimitMb: 500, 77 | directory: _snapshotDirectory, 78 | minDelayBetweenSnapshots: const Duration(seconds: 5), 79 | ), 80 | usageEventsConfig: UsageEventsConfig( 81 | _handleUsageEvent, 82 | deltaMb: 100, 83 | ), 84 | ); 85 | 86 | trackMemoryUsage(config); 87 | } 88 | 89 | void _handleSnapshotEvent(SnapshotEvent event) { 90 | setState(() { 91 | snapshots.add(event); 92 | }); 93 | } 94 | 95 | void _handleUsageEvent(MemoryUsageEvent event) { 96 | setState(() { 97 | usageEvents.add(event); 98 | }); 99 | } 100 | 101 | String _formatSize(int bytes) { 102 | return '${_formatter.format(bytes)} (${_formatter.format(bytes.bytesToMB)} MB)'; 103 | } 104 | 105 | String _formatSnapshots() { 106 | return snapshots.map((snapshot) { 107 | final time = DateFormat('HH:mm:ss').format(snapshot.timestamp); 108 | return '$time : ${snapshot.fileName}'; 109 | }).join('\n'); 110 | } 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | const space = SizedBox(height: 20); 115 | return Scaffold( 116 | appBar: AppBar( 117 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 118 | title: Text(widget.title), 119 | ), 120 | body: Padding( 121 | padding: const EdgeInsets.all(8.0), 122 | child: Column( 123 | crossAxisAlignment: CrossAxisAlignment.start, 124 | children: [ 125 | Text( 126 | 'Current RSS: ${_formatSize(ProcessInfo.currentRss)}', 127 | style: Theme.of(context).textTheme.headlineMedium, 128 | ), 129 | space, 130 | Text( 131 | '-- Configuration --\n$config', 132 | ), 133 | space, 134 | Text( 135 | '-- Usage Events (MB) --\n${usageEvents.map((e) => _formatter.format(e.rss.bytesToMB)).join('; ')}', 136 | ), 137 | space, 138 | Text( 139 | '-- Taken Snapshots --\n${_formatSnapshots()}', 140 | ), 141 | ], 142 | ), 143 | ), 144 | floatingActionButton: FloatingActionButton( 145 | onPressed: _allocateMemory, 146 | tooltip: 'Allocate more memory', 147 | child: const Icon(Icons.add), 148 | ), 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/autosnapshotting/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: autosnapshotting_example 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.2.0 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | cupertino_icons: ^1.0.2 11 | intl: ^0.19.0 12 | memory_usage: any 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^4.0.0 18 | integration_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | uses-material-design: true 23 | 24 | dependency_overrides: 25 | memory_usage: 26 | path: ../../pkgs/memory_usage 27 | -------------------------------------------------------------------------------- /examples/leak_tracking/README.md: -------------------------------------------------------------------------------- 1 | Minimal Flutter application with leak_tracker enabled. 2 | -------------------------------------------------------------------------------- /examples/leak_tracking/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /examples/leak_tracking/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:leak_tracker/leak_tracker.dart'; 4 | 5 | void main() { 6 | LeakTracking.start(); 7 | // Dispatch memory events from the Flutter engine to LeakTracking. 8 | FlutterMemoryAllocations.instance.addListener( 9 | (ObjectEvent event) => LeakTracking.dispatchObjectEvent(event.toMap()), 10 | ); 11 | 12 | runApp(const MyApp()); 13 | } 14 | 15 | class MyApp extends StatelessWidget { 16 | const MyApp({super.key}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MaterialApp( 21 | title: 'Flutter Demo', 22 | theme: ThemeData( 23 | primarySwatch: Colors.blue, 24 | ), 25 | home: const MyHomePage(title: 'Flutter Demo Home Page'), 26 | ); 27 | } 28 | } 29 | 30 | class MyHomePage extends StatefulWidget { 31 | const MyHomePage({super.key, required this.title}); 32 | 33 | final String title; 34 | 35 | @override 36 | State createState() => _MyHomePageState(); 37 | } 38 | 39 | class _MyHomePageState extends State { 40 | int _counter = 0; 41 | 42 | void _incrementCounter() { 43 | setState(() { 44 | _counter++; 45 | }); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | title: Text(widget.title), 53 | ), 54 | body: Center( 55 | child: Column( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | const Text( 59 | 'You have pushed the button this many times:', 60 | ), 61 | Text( 62 | '$_counter', 63 | style: Theme.of(context).textTheme.headlineMedium, 64 | ), 65 | ], 66 | ), 67 | ), 68 | floatingActionButton: FloatingActionButton( 69 | onPressed: _incrementCounter, 70 | tooltip: 'Increment', 71 | child: const Icon(Icons.add), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/leak_tracking/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leak_tracker_minimal_flutter_example 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.2.0 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | # The version is `any` because it is defined by Flutter SDK. 11 | leak_tracker: any 12 | 13 | dev_dependencies: 14 | flutter_lints: ^4.0.0 15 | 16 | flutter: 17 | uses-material-design: true 18 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 11.0.1 2 | 3 | * Add method `tracked` to `LeakTracking`. 4 | 5 | # 11.0.0 6 | 7 | * Remove integration with DevTools and class LeakTrackingTestConfig. 8 | * Update `vm_service` dependency to `>=11.0.0 <16.0.0`. 9 | 10 | # 10.0.8 11 | 12 | * Make link to documentation customizable. 13 | 14 | # 10.0.7 15 | 16 | * Fix broken link in error message. 17 | 18 | # 10.0.6 19 | 20 | * Handle double tracking. 21 | 22 | # 10.0.5 23 | 24 | * Stop failing if finalization happened twice. 25 | 26 | # 10.0.4 27 | 28 | * Add exceptions to test helpers. 29 | 30 | # 10.0.3 31 | 32 | * Improve performance of the case with ignored test helpers. 33 | 34 | # 10.0.2 35 | 36 | * Require Dart SDK 3.2.0 or later. 37 | 38 | # 10.0.1 39 | 40 | * Allow to ignore objects created by test helpers. 41 | 42 | ## 10.0.0 43 | 44 | * Remove `memory_usage`, as it is moved to https://github.com/dart-lang/leak_tracker/tree/main/pkgs/memory_usage. 45 | 46 | ## 9.0.18 47 | 48 | * Update `vm_service` dependency to `>=11.0.0 <15.0.0`. 49 | 50 | ## 9.0.17 51 | 52 | * Move LeakTesting to leak_tracker_testing. 53 | 54 | ## 9.0.16 55 | 56 | * Stub web implementation for retaining path to serve G3. 57 | 58 | ## 9.0.15 59 | 60 | * Fix: debug information should not wipe other settings. 61 | * Add arguments allNotGCed and allNotDisposed to withTracked. 62 | * Remove the dependency on `package:intl`. 63 | * Updated to use `package:lints/recommended.yaml` for analysis. 64 | 65 | ## 9.0.14 66 | 67 | * Remove the dependency on `package:web_socket_channel`. 68 | 69 | ## 9.0.13 70 | 71 | * Define `LeakTesting`. 72 | 73 | ## 9.0.12 74 | 75 | * Update `vm_service` dependency to `>=11.0.0 <14.0.0`. 76 | 77 | ## 9.0.11 78 | 79 | * Remove dependency on logging. 80 | * Avoid double leak tracking. 81 | 82 | ## 9.0.10 83 | 84 | * Use `IgnoredLeaks`. 85 | 86 | ## 9.0.9 87 | 88 | * Define `IgnoredLeaks`. 89 | * Add item `none` to BaseliningMode. 90 | 91 | ## 9.0.8 92 | 93 | * Enable declaring all not disposed objects as leaks. 94 | 95 | ## 9.0.7 96 | 97 | * Use ObjectRecord instead of hash code to identify objects. 98 | * Remove collection of stack trace by class in LeakDiagnosticConfig. 99 | * Bump version of SDK to 3.1.2. 100 | 101 | ## 9.0.6 102 | 103 | * Improve error reporting for connection to vm service. 104 | * Fix misspelling. 105 | * Enable memory baselining. 106 | 107 | ## 9.0.5 108 | 109 | * Fix issue of using wrong settings for a phase, so that the tracker uses settings 110 | at time of object tracking start, instead of current configuration. 111 | 112 | ## 9.0.4 113 | 114 | * Make it possible to disable tracking for a type of leak. 115 | 116 | ## 9.0.3 117 | 118 | * Stop failing if an object is disposed twice. 119 | 120 | ## 9.0.2 121 | 122 | * Make sure phase boundaries are handled correctly. 123 | 124 | ## 9.0.1 125 | 126 | * Auto-start VM Service when needed. 127 | 128 | ## 9.0.0 129 | 130 | * Refactor to improve performance of regression tests with leak tracking. 131 | * Remove API that is not used in Flutter Framework. 132 | * Rename `LeakTrackingConfiguration` to `LeakTrackingConfig`. 133 | * Remove global flag [collectDebugInformationForLeaks]. 134 | * Rename `checkNonGCed` to `checkNotGCed` and `collectRetainingPathForNonGCed` to `collectRetainingPathForNotGCed`. 135 | * Group global items related to leak tracking, in abstract class LeakTracking. 136 | * Rename `gcCountBuffer` to `numberOfGcCycles` and `disposalTimeBuffer` to `disposalTime`. 137 | 138 | ## 8.0.3 139 | 140 | * Fix an issue with custom gcCountBuffer values. 141 | 142 | ## 8.0.2 143 | 144 | * Improve performance. 145 | * Make gcCountBuffer customizable with default value 3. 146 | 147 | ## 8.0.1 148 | 149 | * Handle SentinelException for retaining path. 150 | * Limit number of requests for retaining path. 151 | 152 | ## 8.0.0 153 | 154 | * Enable turn on/off tracking for leak types. 155 | * Put all global flags into one class. 156 | 157 | ## 7.0.8 158 | 159 | * Disconnect from service after obtaining retaining paths. 160 | * Protect from identityHashCode equal to 0. 161 | 162 | ## 7.0.6 163 | 164 | * Add helpers for troubleshooting. 165 | * Handle generic arguments for retaining path detection. 166 | * Convert to multi-package. 167 | 168 | ## 7.0.4 169 | 170 | * Fix path collection. 171 | * Create constructor to collect path. 172 | * Fix connection issue. 173 | * Improve retaining path formatting. 174 | * Format retaining path nicely. 175 | * Enable collection of retaining path. 176 | * Separate testing. 177 | * Fixes to support g3. 178 | * Fix for MemoryUsageEvent constructor. 179 | 180 | ## 6.0.0 181 | 182 | * Fix typo in public API. 183 | * Add assertion for negative delay between snapshots. 184 | 185 | ## 5.0.0 186 | 187 | * Migrate from auto-snapshotting to usage-tracking. 188 | * Improve leak debugging UX. 189 | * Fix failures in case of duplicates. 190 | 191 | ## 4.0.3 192 | 193 | * Fix broken documentation link. 194 | 195 | ## 4.0.2 196 | 197 | * Improve documentation. 198 | 199 | ## 4.0.1 200 | 201 | * Autosnapshotting. 202 | 203 | ## 4.0.0 204 | 205 | * Improve documentation and naming. 206 | 207 | ## 3.0.2 208 | 209 | * Add members to `LeakTrackingTestConfig`. 210 | 211 | ## 3.0.1 212 | 213 | * Increase sdk version 214 | * Remove obsolete lint 215 | 216 | ## 3.0.0 217 | 218 | * Breaking changes: update names of types to be align with Flutter naming convention. 219 | * Add model for Flutter unit testing configuration. 220 | * Adopt Flutter standard lints. 221 | * Improve documentation. 222 | 223 | ## 2.0.1 224 | 225 | * Minor changes. 226 | * Updated `vm_service` version to >=9.0.0 <12.0.0. 227 | 228 | ## 2.0.0 229 | 230 | * Breaking changes in `withLeakTracking`. 231 | * Refactor test_infra libraries. 232 | * Documentation updates. 233 | 234 | ## 1.0.0 235 | 236 | * First release. 237 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/leak_tracker.svg)](https://pub.dev/packages/leak_tracker) 2 | [![package publisher](https://img.shields.io/pub/publisher/leak_tracker.svg)](https://pub.dev/packages/leak_tracker/publisher) 3 | 4 | ## What is this? 5 | 6 | This is a framework for detecting memory issues in Dart and Flutter applications. 7 | 8 | Read more in [leak tracking overview](https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/OVERVIEW.md). 9 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | 8 | linter: 9 | rules: 10 | - avoid_print 11 | - sort_constructors_first 12 | - sort_unnamed_constructors_first 13 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/example/README.md: -------------------------------------------------------------------------------- 1 | Minimal Dart application with leak_tracker enabled. 2 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | 7 | void main(List arguments) { 8 | LeakTracking.start(); 9 | print('Hello, world!'); 10 | LeakTracking.stop(); 11 | } 12 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leak_tracker_minimal_dart_example 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.2.0 6 | 7 | dependencies: 8 | leak_tracker: any 9 | 10 | dev_dependencies: 11 | lints: ^4.0.0 12 | 13 | dependency_overrides: 14 | leak_tracker: 15 | path: .. 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | leak_tracker.dart-->src; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/leak_tracker.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/leak_tracking/helpers.dart'; 6 | export 'src/leak_tracking/leak_tracking.dart'; 7 | export 'src/leak_tracking/primitives/model.dart'; 8 | export 'src/shared/shared_model.dart'; 9 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | leak_tracking-->shared; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | _baseliner.dart-->primitives; 9 | _leak_filter.dart-->_object_record.dart; 10 | _leak_filter.dart-->primitives; 11 | _leak_reporter.dart-->primitives; 12 | _leak_tracker.dart-->_leak_reporter.dart; 13 | _leak_tracker.dart-->_object_tracker.dart; 14 | _leak_tracker.dart-->primitives; 15 | _object_record.dart-->primitives; 16 | _object_record_set.dart-->_object_record.dart; 17 | _object_record_set.dart-->primitives; 18 | _object_records.dart-->_object_record.dart; 19 | _object_records.dart-->_object_record_set.dart; 20 | _object_tracker.dart-->_leak_filter.dart; 21 | _object_tracker.dart-->_object_record.dart; 22 | _object_tracker.dart-->_object_records.dart; 23 | _object_tracker.dart-->primitives; 24 | helpers.dart-->primitives; 25 | leak_tracking.dart-->_baseliner.dart; 26 | leak_tracking.dart-->_leak_tracker.dart; 27 | leak_tracking.dart-->_object_record.dart; 28 | leak_tracking.dart-->primitives; 29 | ``` 30 | 31 | ### Inversions 32 | In this folder: 0 33 | 34 | Including sub-folders: 0 35 | 36 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_baseliner.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: avoid_print 6 | 7 | import 'dart:io'; 8 | 9 | import 'primitives/_print_bytes.dart'; 10 | import 'primitives/model.dart'; 11 | 12 | class Baseliner { 13 | Baseliner._(this.baselining) 14 | : assert(baselining.mode != BaseliningMode.none), 15 | rss = ValueSampler.start(initialValue: _currentRss()); 16 | 17 | final MemoryBaselining baselining; 18 | final ValueSampler rss; 19 | 20 | static Baseliner? finishOldAndStartNew( 21 | Baseliner? oldBaseliner, 22 | MemoryBaselining? baselining, 23 | ) { 24 | oldBaseliner?._finish(); 25 | if (baselining == null || baselining.mode == BaseliningMode.none) { 26 | return null; 27 | } 28 | return Baseliner._(baselining); 29 | } 30 | 31 | void takeSample() { 32 | rss.add(_currentRss()); 33 | } 34 | 35 | void _finish() { 36 | switch (baselining.mode) { 37 | case BaseliningMode.measure: 38 | if (baselining.baseline != null) { 39 | print('$_asComparison\n\n\n'); 40 | print('\n\n'); 41 | } 42 | print(asDartCode()); 43 | case BaseliningMode.regression: 44 | throw UnimplementedError( 45 | 'Regression testing for memory consumption is not implemented yet. ' 46 | 'Upvote the following issue if interested: https://github.com/dart-lang/leak_tracker/issues/120'); 47 | case BaseliningMode.none: 48 | } 49 | } 50 | 51 | static int _currentRss() => ProcessInfo.currentRss; 52 | 53 | String asDartCode() { 54 | return '''To set as the new baseline, set the following parameter of $MemoryBaselining: 55 | baseline: $MemoryBaseline( 56 | rss: ${rss.asDartCode()}, 57 | )'''; 58 | } 59 | 60 | String _asComparison() { 61 | final baseline = baselining.baseline; 62 | if (baseline == null) throw StateError('Baseline is not set.'); 63 | final golden = baseline.rss; 64 | final current = rss; 65 | final buffer = StringBuffer(); 66 | 67 | final byteEntries = [ 68 | ('initialValue', current.initialValue, golden.initialValue), 69 | ('deltaAvg', current.deltaAvg, golden.deltaAvg), 70 | ('deltaMax', current.deltaMax, golden.deltaMax), 71 | ('absAvg', current.absAvg, golden.absAvg), 72 | ('absMax', current.absMax, golden.absMax), 73 | ]; 74 | 75 | for (final e in byteEntries) { 76 | final (label, current, golden) = e; 77 | buffer.writeln(_asDelta(label, current, golden)); 78 | } 79 | 80 | buffer.writeln( 81 | 'samples: ${current.samples} - ${golden.samples} = ' 82 | '${current.samples - golden.samples}', 83 | ); 84 | return buffer.toString(); 85 | } 86 | 87 | String _asDelta(String name, num current, num golden) { 88 | String format(num size) => prettyPrintBytes(size, includeUnit: true) ?? ''; 89 | final delta = current - golden; 90 | final deltaPercent = (delta / golden * 100).toStringAsFixed(2); 91 | return '$name: ${format(current)} - ${format(golden)} = ' 92 | '${format(delta)} ($deltaPercent%)'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_leak_filter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file.s 4 | 5 | import '../shared/shared_model.dart'; 6 | import '_object_record.dart'; 7 | import 'primitives/model.dart'; 8 | 9 | /// Decides which leaks to report based on allow lists of the phase. 10 | class LeakFilter { 11 | final Map _phases = {}; 12 | 13 | /// Returns true if the leak should be reported. 14 | bool shouldReport(LeakType leakType, ObjectRecord record) { 15 | final filter = _phases.putIfAbsent( 16 | record.phase, 17 | () => _PhaseLeakFilter(record.phase), 18 | ); 19 | return filter.shouldReport(leakType, record); 20 | } 21 | } 22 | 23 | class _PhaseLeakFilter { 24 | _PhaseLeakFilter(this.phase); 25 | 26 | /// Number of leaks by (object type, leak type) for limited allowlists. 27 | final _count = <(String, LeakType), int>{}; 28 | 29 | final PhaseSettings phase; 30 | 31 | bool shouldReport(LeakType leakType, ObjectRecord record) { 32 | final bool result; 33 | switch (leakType) { 34 | case LeakType.notDisposed: 35 | result = _shouldReportByTypeAndClass( 36 | leakType, 37 | record, 38 | phase.ignoredLeaks.notDisposed, 39 | ); 40 | case LeakType.notGCed: 41 | case LeakType.gcedLate: 42 | result = _shouldReportByTypeAndClass( 43 | leakType, 44 | record, 45 | phase.ignoredLeaks.experimentalNotGCed, 46 | ); 47 | } 48 | 49 | // Check for test helpers should happen only in case of leak because 50 | // it is performance heavy. 51 | // TODO(polina-c): add a test to ensure that the test helper check does not 52 | // run when this is false 53 | // https://github.com/dart-lang/leak_tracker/issues/210 54 | final shouldCheckCreator = 55 | phase.ignoredLeaks.createdByTestHelpers && result; 56 | 57 | if (!shouldCheckCreator) return result; 58 | 59 | final createdByTestHelpers = 60 | record.creationChecker?.createdByTestHelpers ?? false; 61 | return !createdByTestHelpers; 62 | } 63 | 64 | /// Returns whether the leak should be reported based on its type and class. 65 | bool _shouldReportByTypeAndClass( 66 | LeakType leakType, 67 | ObjectRecord record, 68 | IgnoredLeaksSet ignoredLeaks, 69 | ) { 70 | assert(record.phase == phase); 71 | if (ignoredLeaks.ignoreAll) return false; 72 | final objectType = record.type.toString(); 73 | if (!ignoredLeaks.byClass.containsKey(objectType)) return true; 74 | final allowedCount = ignoredLeaks.byClass[objectType]; 75 | if (allowedCount == null) return false; 76 | 77 | final actualCount = _count.update( 78 | (objectType, leakType), 79 | (value) => value + 1, 80 | ifAbsent: () => 1, 81 | ); 82 | 83 | return actualCount > allowedCount; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_leak_reporter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import '../shared/_util.dart'; 8 | import '../shared/shared_model.dart'; 9 | import 'primitives/model.dart'; 10 | 11 | /// Checks [leakProvider] either by schedule or by request. 12 | /// 13 | /// If there are leaks, reports them to the enabled outputs: 14 | /// listener and console. 15 | class LeakReporter { 16 | LeakReporter({ 17 | required this.leakProvider, 18 | required this.checkPeriod, 19 | required this.onLeaks, 20 | required this.stdoutSink, 21 | }) { 22 | final period = checkPeriod; 23 | _timer = period == null 24 | ? null 25 | : Timer.periodic(period, (_) async => await checkLeaks()); 26 | } 27 | 28 | late final Timer? _timer; 29 | 30 | LeakSummary _previousResult = LeakSummary({}); 31 | 32 | /// Period to check for leaks. 33 | /// 34 | /// If null, there is no periodic checking. 35 | final Duration? checkPeriod; 36 | 37 | // If not null, then the leak summary will be printed here, when 38 | // leak totals change. 39 | final StdoutSummarySink? stdoutSink; 40 | 41 | /// Listener for leaks. 42 | /// 43 | /// Will be invoked if leak totals change. 44 | final LeakSummaryCallback? onLeaks; 45 | 46 | final LeakProvider leakProvider; 47 | 48 | /// Checks leaks, if there are new ones, send notifications. 49 | Future checkLeaks() async { 50 | final summary = await leakProvider.leaksSummary(); 51 | 52 | if (!summary.matches(_previousResult)) { 53 | onLeaks?.call(summary); 54 | stdoutSink?.send(summary); 55 | 56 | _previousResult = summary; 57 | } 58 | return summary; 59 | } 60 | 61 | void dispose() { 62 | _timer?.cancel(); 63 | } 64 | } 65 | 66 | class StdoutSummarySink { 67 | void send(LeakSummary summary) => printToConsole(summary.toMessage()); 68 | } 69 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_leak_tracker.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../shared/_primitives.dart'; 6 | import '_leak_reporter.dart'; 7 | import '_object_tracker.dart'; 8 | import 'primitives/model.dart'; 9 | 10 | class LeakTracker { 11 | LeakTracker(LeakTrackingConfig config, ObjectRef phase) { 12 | objectTracker = ObjectTracker( 13 | disposalTime: config.disposalTime, 14 | numberOfGcCycles: config.numberOfGcCycles, 15 | maxRequestsForRetainingPath: config.maxRequestsForRetainingPath, 16 | ); 17 | 18 | leakReporter = LeakReporter( 19 | leakProvider: objectTracker, 20 | checkPeriod: config.checkPeriod, 21 | onLeaks: config.onLeaks, 22 | stdoutSink: config.stdoutLeaks ? StdoutSummarySink() : null, 23 | ); 24 | } 25 | 26 | late final ObjectTracker objectTracker; 27 | 28 | late final LeakReporter leakReporter; 29 | 30 | void dispose() { 31 | objectTracker.dispose(); 32 | leakReporter.dispose(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_object_record.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../shared/_primitives.dart'; 6 | import '../shared/shared_model.dart'; 7 | import 'primitives/_gc_counter.dart'; 8 | import 'primitives/_test_helper_detector.dart'; 9 | import 'primitives/model.dart'; 10 | 11 | /// Information about an object, tracked for leaks. 12 | class ObjectRecord { 13 | ObjectRecord( 14 | Object object, 15 | this.context, 16 | this.trackedClass, 17 | this.phase, { 18 | this.creationChecker, 19 | }) : ref = WeakReference(object), 20 | type = object.runtimeType, 21 | code = identityHashCode(object); 22 | 23 | /// Weak reference to the tracked object. 24 | final WeakReference ref; 25 | 26 | /// [IdentityHashCode] of the object. 27 | /// 28 | /// Is needed to help debugging notDisposed leak, for which 29 | /// the object is already GCed and thus there is no access to its code. 30 | final IdentityHashCode code; 31 | 32 | /// [CreationChecker] that contains knowledge about creation. 33 | /// 34 | /// Is not used in the record, but can be used 35 | /// by owners of this object. 36 | final CreationChecker? creationChecker; 37 | 38 | Map? context; 39 | 40 | final PhaseSettings phase; 41 | 42 | /// Runtime type of the tracked object. 43 | final Type type; 44 | 45 | final String trackedClass; 46 | 47 | DateTime? _disposalTime; 48 | int? _disposalGcCount; 49 | 50 | void setDisposed(int gcTime, DateTime time) { 51 | if (_disposalGcCount != null) { 52 | // It is not responsibility of leak tracker to check for double disposal. 53 | return; 54 | } 55 | if (_gcedGcCount != null) { 56 | throw Exception( 57 | 'The object $code should not be disposed after being GCed'); 58 | } 59 | _disposalGcCount = gcTime; 60 | _disposalTime = time; 61 | } 62 | 63 | DateTime? _gcedTime; 64 | int? _gcedGcCount; 65 | void setGCed(int gcCount, DateTime time) { 66 | if (_gcedGcCount != null) { 67 | throw Exception('$trackedClass, $code is GCed twice'); 68 | } 69 | _gcedGcCount = gcCount; 70 | _gcedTime = time; 71 | } 72 | 73 | bool get isGCed => _gcedGcCount != null; 74 | bool get isDisposed => _disposalGcCount != null; 75 | 76 | bool isGCedLateLeak(Duration disposalTime, int numberOfGcCycles) { 77 | if (_disposalGcCount == null || _gcedGcCount == null) return false; 78 | assert(_gcedTime != null); 79 | return shouldObjectBeGced( 80 | gcCountAtDisposal: _disposalGcCount!, 81 | timeAtDisposal: _disposalTime!, 82 | currentGcCount: _gcedGcCount!, 83 | currentTime: _gcedTime!, 84 | disposalTime: disposalTime, 85 | numberOfGcCycles: numberOfGcCycles, 86 | ); 87 | } 88 | 89 | bool isNotGCedLeak( 90 | int currentGcCount, 91 | DateTime currentTime, 92 | Duration disposalTime, 93 | int numberOfGcCycles, 94 | ) { 95 | if (_gcedGcCount != null) return false; 96 | return shouldObjectBeGced( 97 | gcCountAtDisposal: _disposalGcCount!, 98 | timeAtDisposal: _disposalTime!, 99 | currentGcCount: currentGcCount, 100 | currentTime: currentTime, 101 | disposalTime: disposalTime, 102 | numberOfGcCycles: numberOfGcCycles, 103 | ); 104 | } 105 | 106 | bool get isNotDisposedLeak { 107 | return isGCed && !isDisposed; 108 | } 109 | 110 | void setContext(String key, Object value) { 111 | final theContext = context ?? {}; 112 | theContext[key] = value; 113 | context = theContext; 114 | } 115 | 116 | void mergeContext(Map? addedContext) { 117 | if (addedContext == null) return; 118 | final theContext = context; 119 | if (theContext == null) { 120 | context = addedContext; 121 | return; 122 | } 123 | theContext.addAll(addedContext); 124 | } 125 | 126 | LeakReport toLeakReport() => LeakReport( 127 | type: type.toString(), 128 | context: context, 129 | code: code, 130 | trackedClass: trackedClass, 131 | phase: phase.name, 132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_object_record_set.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:meta/meta.dart'; 7 | 8 | import '../shared/_primitives.dart'; 9 | import '_object_record.dart'; 10 | import 'primitives/_test_helper_detector.dart'; 11 | import 'primitives/model.dart'; 12 | 13 | @visibleForTesting 14 | class ObjectRecordSet { 15 | ObjectRecordSet({@visibleForTesting this.coder = standardIdentityHashCoder}); 16 | 17 | final IdentityHashCoder coder; 18 | 19 | final _records = >{}; 20 | 21 | bool contains(ObjectRecord record) { 22 | final list = _records[record.code]; 23 | if (list == null) return false; 24 | return list.contains(record); 25 | } 26 | 27 | ObjectRecord? record(Object object) { 28 | final code = identityHashCode(object); 29 | 30 | final list = _records[code]; 31 | if (list == null) return null; 32 | 33 | return list.firstWhereOrNull((r) => r.ref.target == object); 34 | } 35 | 36 | /// Removes record if it exists in the set. 37 | void remove(ObjectRecord record) { 38 | final list = _records[record.code]; 39 | if (list == null) return; 40 | var removed = false; 41 | list.removeWhere((r) { 42 | if (r == record) { 43 | assert(!removed); 44 | removed = true; 45 | } 46 | return r == record; 47 | }); 48 | _length--; 49 | if (list.isEmpty) _records.remove(record.code); 50 | } 51 | 52 | ({ObjectRecord record, bool wasAbsent}) putIfAbsent( 53 | Object object, 54 | Map? context, 55 | PhaseSettings phase, 56 | String trackedClass, 57 | ) { 58 | final code = identityHashCode(object); 59 | 60 | final list = _records.putIfAbsent(code, () => []); 61 | 62 | final existing = 63 | list.firstWhereOrNull((r) => identical(r.ref.target, object)); 64 | if (existing != null) return (record: existing, wasAbsent: false); 65 | 66 | final creationChecker = phase.ignoredLeaks.createdByTestHelpers 67 | ? CreationChecker( 68 | creationStack: StackTrace.current, 69 | exceptions: phase.ignoredLeaks.testHelperExceptions) 70 | : null; 71 | 72 | final result = ObjectRecord( 73 | object, 74 | context, 75 | trackedClass, 76 | phase, 77 | creationChecker: creationChecker, 78 | ); 79 | 80 | list.add(result); 81 | _length++; 82 | return (record: result, wasAbsent: true); 83 | } 84 | 85 | int _length = 0; 86 | int get length => _length; 87 | 88 | Iterable toIterable() { 89 | return _records.values.expand((list) => list); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/_object_records.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '_object_record.dart'; 6 | import '_object_record_set.dart'; 7 | 8 | /// Object collections to track leaks. 9 | /// 10 | /// Objects migrate between collections based on their state. 11 | /// 12 | /// On registration, each object enters the collections [notGCed]. 13 | /// On disposal it is added to [notGCedDisposedOk]. Then, if it is overdue 14 | /// to be GCed it migrates from to [notGCedDisposedLate]. 15 | /// Then, if the leak is collected, it 16 | /// migrates to [notGCedDisposedLateCollected]. 17 | /// 18 | /// If the object gets GCed, it is removed from all notGCed... collections, 19 | /// and, if it was GCed wrongly, added to one of gced... collections. 20 | class ObjectRecords { 21 | /// All not GCed objects. 22 | final notGCed = ObjectRecordSet(); 23 | 24 | /// Not GCed objects, that were disposed and are not expected to be GCed yet. 25 | final notGCedDisposedOk = {}; 26 | 27 | /// Not GCed objects, that were disposed and are overdue to be GCed. 28 | final notGCedDisposedLate = {}; 29 | 30 | /// Not GCed objects, that were disposed, are overdue to be GCed, 31 | /// and were collected as nonGCed leaks. 32 | final notGCedDisposedLateCollected = {}; 33 | 34 | /// GCed objects that were late to be GCed. 35 | final List gcedLateLeaks = []; 36 | 37 | /// GCed objects that were not disposed. 38 | final List gcedNotDisposedLeaks = []; 39 | 40 | void _assertNotWatchedToBeGCed(ObjectRecord record) { 41 | assert(() { 42 | assert(!notGCed.contains(record)); 43 | assert(!notGCedDisposedOk.contains(record)); 44 | assert(!notGCedDisposedLate.contains(record)); 45 | assert(!notGCedDisposedLateCollected.contains(record)); 46 | return true; 47 | }()); 48 | } 49 | 50 | void assertRecordIntegrity(ObjectRecord record) { 51 | assert(() { 52 | final notGCedSetMembership = 53 | (notGCedDisposedOk.contains(record) ? 1 : 0) + 54 | (notGCedDisposedLate.contains(record) ? 1 : 0) + 55 | (notGCedDisposedLateCollected.contains(record) ? 1 : 0); 56 | 57 | assert(notGCedSetMembership <= 1); 58 | 59 | if (notGCedSetMembership == 1) { 60 | assert(notGCed.contains(record)); 61 | } 62 | 63 | return true; 64 | }()); 65 | } 66 | 67 | void assertIntegrity() { 68 | assert(() { 69 | notGCed.toIterable().forEach(assertRecordIntegrity); 70 | gcedLateLeaks.forEach(_assertNotWatchedToBeGCed); 71 | gcedNotDisposedLeaks.forEach(_assertNotWatchedToBeGCed); 72 | return true; 73 | }()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/helpers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:developer'; 7 | 8 | import '../shared/_formatting.dart'; 9 | import 'primitives/_retaining_path/_connection.dart'; 10 | import 'primitives/_retaining_path/_retaining_path.dart'; 11 | 12 | /// Forces garbage collection by aggressive memory allocation. 13 | /// 14 | /// Verifies that garbage collection happened using [reachabilityBarrier]. 15 | /// Does not work in web and in release mode. 16 | /// 17 | /// Use [timeout] to limit waiting time. 18 | /// Use [fullGcCycles] to force multiple garbage collections. 19 | /// 20 | /// The method is helpful for testing in combination with [WeakReference] to 21 | /// ensure an object is not held by another object from garbage collection. 22 | /// 23 | /// For code example see 24 | /// https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/TROUBLESHOOT.md 25 | Future forceGC({ 26 | Duration? timeout, 27 | int fullGcCycles = 1, 28 | }) async { 29 | final stopwatch = timeout == null ? null : (Stopwatch()..start()); 30 | final barrier = reachabilityBarrier; 31 | 32 | final storage = >[]; 33 | 34 | void allocateMemory() { 35 | storage.add(List.generate(30000, (n) => n)); 36 | if (storage.length > 100) { 37 | storage.removeAt(0); 38 | } 39 | } 40 | 41 | while (reachabilityBarrier < barrier + fullGcCycles) { 42 | if ((stopwatch?.elapsed ?? Duration.zero) > (timeout ?? Duration.zero)) { 43 | throw TimeoutException('forceGC timed out', timeout); 44 | } 45 | await Future.delayed(Duration.zero); 46 | allocateMemory(); 47 | } 48 | } 49 | 50 | /// Returns nicely formatted retaining path for the [WeakReference.target]. 51 | /// 52 | /// If the object is garbage collected or not retained, returns null. 53 | /// 54 | /// Does not work in web and in release mode. 55 | /// 56 | /// To run this inside `flutter test` pass `--enable-vmservice`. 57 | /// 58 | /// Also does not work for objects that are not returned by getInstances. 59 | /// https://github.com/dart-lang/sdk/blob/3e80d29fd6fec56187d651ce22ea81f1e8732214/runtime/vm/object_graph.cc#L1803 60 | Future formattedRetainingPath(WeakReference ref) async { 61 | if (ref.target == null) return null; 62 | final connection = await connect(); 63 | final path = await retainingPath( 64 | connection, 65 | ref.target, 66 | ); 67 | 68 | if (path == null) return null; 69 | return retainingPathToString(path); 70 | } 71 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/README.md: -------------------------------------------------------------------------------- 1 | Items (folders or libraries) in this folder do not depend on each other 2 | and on other libraries in `leak_tracking`. 3 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_dispatcher.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // Values in [FieldNames] and [EventType] should be identical to ones used in 6 | // https://github.com/flutter/flutter/blob/a479718b02a818fb4ac8d4900bf08ca389cd8e7d/packages/flutter/lib/src/foundation/memory_allocations.dart#L23 7 | 8 | class _FieldNames { 9 | static const String eventType = 'eventType'; 10 | static const String libraryName = 'libraryName'; 11 | static const String className = 'className'; 12 | } 13 | 14 | class _EventType { 15 | static const String created = 'created'; 16 | static const String disposed = 'disposed'; 17 | } 18 | 19 | typedef StartTrackingCallback = void Function({ 20 | required Object object, 21 | required Map? context, 22 | required String library, 23 | required String className, 24 | }); 25 | 26 | typedef DispatchDisposalCallback = void Function({ 27 | required Object object, 28 | required Map? context, 29 | }); 30 | 31 | void dispatchObjectEvent( 32 | Map> event, { 33 | required StartTrackingCallback onStartTracking, 34 | required DispatchDisposalCallback onDispatchDisposal, 35 | }) { 36 | assert(event.length == 1); 37 | final entry = event.entries.first; 38 | 39 | final object = entry.key; 40 | final fields = entry.value; 41 | 42 | final eventType = fields[_FieldNames.eventType] as String; 43 | 44 | final libraryName = fields[_FieldNames.libraryName]?.toString() ?? ''; 45 | final className = fields[_FieldNames.className]?.toString() ?? ''; 46 | 47 | if (eventType == _EventType.created) { 48 | onStartTracking( 49 | object: object, 50 | context: null, 51 | library: libraryName, 52 | className: className, 53 | ); 54 | } else if (eventType == _EventType.disposed) { 55 | onDispatchDisposal(object: object, context: null); 56 | } else { 57 | throw StateError('Unexpected event type for $object: $eventType.'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_finalizer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | typedef ObjectGcCallback = void Function(Object token); 6 | 7 | /// Finalizer builder to mock standard [Finalizer]. 8 | typedef FinalizerBuilder = FinalizerWrapper Function( 9 | ObjectGcCallback onObjectGc, 10 | ); 11 | 12 | FinalizerWrapper buildStandardFinalizer(ObjectGcCallback onObjectGc) => 13 | StandardFinalizerWrapper(onObjectGc); 14 | 15 | abstract class FinalizerWrapper { 16 | void attach(Object object, Object token); 17 | } 18 | 19 | class StandardFinalizerWrapper implements FinalizerWrapper { 20 | StandardFinalizerWrapper(ObjectGcCallback onObjectGc) 21 | : _finalizer = Finalizer(onObjectGc); 22 | 23 | final Finalizer _finalizer; 24 | 25 | @override 26 | void attach(Object object, Object token) { 27 | _finalizer.attach(object, token); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_gc_counter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:developer'; 6 | 7 | /// Wrapper for reachabilityBarrier, for mocking purposes. 8 | class GcCounter { 9 | /// Number of full GC cycles since start of the isolate. 10 | int get gcCount => reachabilityBarrier; 11 | } 12 | 13 | /// True, if the disposed object is expected to be GCed, 14 | /// assuming at the disposal moment it was referenced only 15 | /// by the the disposal invoker. 16 | bool shouldObjectBeGced({ 17 | required int gcCountAtDisposal, 18 | required DateTime timeAtDisposal, 19 | required int currentGcCount, 20 | required DateTime currentTime, 21 | required Duration disposalTime, 22 | required int numberOfGcCycles, 23 | }) => 24 | currentGcCount - gcCountAtDisposal >= numberOfGcCycles && 25 | currentTime.difference(timeAtDisposal) >= disposalTime; 26 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_print_bytes.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | String? prettyPrintBytes( 6 | num? bytes, { 7 | int kbFractionDigits = 0, 8 | int mbFractionDigits = 1, 9 | int gbFractionDigits = 1, 10 | bool includeUnit = false, 11 | num roundingPoint = 1.0, 12 | int maxBytes = 52, 13 | }) { 14 | if (bytes == null) { 15 | return null; 16 | } 17 | // TODO(peterdjlee): Generalize to handle different kbFractionDigits. 18 | // Ensure a small number of bytes does not print as 0 KB. 19 | // If bytes >= maxBytes and kbFractionDigits == 1, 20 | // it will start rounding to 0.1 KB. 21 | if (bytes.abs() < maxBytes && kbFractionDigits == 1) { 22 | var output = bytes.toString(); 23 | if (includeUnit) { 24 | output += ' B'; 25 | } 26 | return output; 27 | } 28 | final sizeInKB = bytes.abs() / 1024.0; 29 | final sizeInMB = sizeInKB / 1024.0; 30 | final sizeInGB = sizeInMB / 1024.0; 31 | 32 | if (sizeInGB >= roundingPoint) { 33 | return printGB( 34 | bytes, 35 | fractionDigits: gbFractionDigits, 36 | includeUnit: includeUnit, 37 | ); 38 | } else if (sizeInMB >= roundingPoint) { 39 | return printMB( 40 | bytes, 41 | fractionDigits: mbFractionDigits, 42 | includeUnit: includeUnit, 43 | ); 44 | } else { 45 | return printKB( 46 | bytes, 47 | fractionDigits: kbFractionDigits, 48 | includeUnit: includeUnit, 49 | ); 50 | } 51 | } 52 | 53 | String printKB(num bytes, {int fractionDigits = 0, bool includeUnit = false}) { 54 | // We add ((1024/2)-1) to the value before formatting so that a non-zero byte 55 | // value doesn't round down to 0. If showing decimal points, let it round 56 | // normally. 57 | // TODO(peterdjlee): Round up to the respective digit when fractionDigits > 0. 58 | final processedBytes = fractionDigits == 0 ? bytes + 511 : bytes; 59 | var output = (processedBytes / 1024.0).toStringAsFixed(fractionDigits); 60 | if (includeUnit) { 61 | output += ' KB'; 62 | } 63 | return output; 64 | } 65 | 66 | String printMB(num bytes, {int fractionDigits = 1, bool includeUnit = false}) { 67 | var output = (bytes / (1024 * 1024.0)).toStringAsFixed(fractionDigits); 68 | if (includeUnit) { 69 | output += ' MB'; 70 | } 71 | return output; 72 | } 73 | 74 | String printGB(num bytes, {int fractionDigits = 1, bool includeUnit = false}) { 75 | var output = 76 | (bytes / (1024 * 1024.0 * 1024.0)).toStringAsFixed(fractionDigits); 77 | if (includeUnit) { 78 | output += ' GB'; 79 | } 80 | return output; 81 | } 82 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_retaining_path/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | _retaining_path.dart-->_retaining_path_web.dart; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_retaining_path/_connection.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:developer'; 7 | 8 | import 'package:vm_service/vm_service.dart'; 9 | import 'package:vm_service/vm_service_io.dart'; 10 | 11 | Future _serviceUri() async { 12 | var uri = (await Service.getInfo()).serverWebSocketUri; 13 | if (uri != null) return uri; 14 | 15 | uri = (await Service.controlWebServer(enable: true)).serverWebSocketUri; 16 | if (uri != null) return uri; 17 | 18 | throw StateError( 19 | 'Could not start VM service. ' 20 | 'If you are running `flutter test`, pass the flag `--enable-vmservice`', 21 | ); 22 | } 23 | 24 | /// Connects to vm service protocol. 25 | /// 26 | /// If the VM service is not found, tries to start it. 27 | Future connect() async { 28 | final uri = await _serviceUri(); 29 | 30 | final service = await vmServiceConnectUri(uri.toString()); 31 | 32 | // Warming up and validating the connection. 33 | await service.getVersion(); 34 | 35 | return service; 36 | } 37 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_retaining_path/_retaining_path.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:vm_service/vm_service.dart'; 6 | 7 | import '_retaining_path_web.dart' 8 | if (dart.library.isolate) '_retaining_path_isolate.dart'; 9 | 10 | /// Returns retaining path for an object, if it can be detected. 11 | /// 12 | /// If [object] is null or object reference cannot be obtained or 13 | /// isolate cannot be obtained, returns null. 14 | Future retainingPath( 15 | VmService service, 16 | Object? object, 17 | ) async { 18 | return retainingPathImpl(service, object); 19 | } 20 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_retaining_path/_retaining_path_isolate.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:developer'; 7 | import 'dart:isolate'; 8 | 9 | import 'package:vm_service/vm_service.dart' hide Isolate; 10 | 11 | /// Returns retaining path for an object, if it can be detected. 12 | /// 13 | /// If [object] is null or object reference cannot be obtained or 14 | /// isolate cannot be obtained, returns null. 15 | Future retainingPathImpl( 16 | VmService service, 17 | Object? object, 18 | ) async { 19 | if (object == null) return null; 20 | 21 | final objRef = Service.getObjectId(object); 22 | 23 | if (objRef == null) return null; 24 | 25 | try { 26 | final isolateId = Service.getIsolateId(Isolate.current); 27 | 28 | if (isolateId == null) { 29 | return null; 30 | } 31 | 32 | final result = await service.getRetainingPath( 33 | isolateId, 34 | objRef, 35 | 100000, 36 | ); 37 | 38 | return result; 39 | } on SentinelException { 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_retaining_path/_retaining_path_web.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:vm_service/vm_service.dart'; 8 | 9 | /// Returns retaining path for an object, if it can be detected. 10 | /// 11 | /// If [object] is null or object reference cannot be obtained or 12 | /// isolate cannot be obtained, returns null. 13 | Future retainingPathImpl( 14 | VmService service, 15 | Object? object, 16 | ) async { 17 | return null; 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/leak_tracking/primitives/_test_helper_detector.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:meta/meta.dart'; 6 | 7 | /// Frames pointing the folder `test` or the package `flutter_test`. 8 | final _testHelperFrame = RegExp( 9 | r'(?:' + 10 | RegExp.escape(r'/test/') + 11 | r'|' + 12 | RegExp.escape(r'(package:flutter_test/') + 13 | r')', 14 | ); 15 | 16 | /// Frames that match [_testHelperFrame], but are not test helpers. 17 | final _exceptions = RegExp( 18 | r'(?:' 19 | r'AutomatedTestWidgetsFlutterBinding.\w|' 20 | r'TestWidgetsFlutterBinding.\w|' 21 | r'TestAsyncUtils.\w|' 22 | r'WidgetTester.\w|' 23 | r'testWidgets.' 24 | ')', 25 | ); 26 | 27 | /// Test body or closure inside test body. 28 | final _startFrame = RegExp( 29 | r'(?:' 30 | r'TestAsyncUtils.guard.|' 31 | r' main.' 32 | r')', 33 | ); 34 | 35 | /// Returns whether the leak reported by [objectCreationTrace] 36 | /// was created by a test helper. 37 | /// 38 | /// Frames, that match [exceptions] will be ignored. 39 | /// 40 | /// See details on what means to be created by a test helper 41 | /// in doc for `LeakTesting.createdByTestHelpers`. 42 | @visibleForTesting 43 | bool isCreatedByTestHelper( 44 | String objectCreationTrace, 45 | List exceptions, 46 | ) { 47 | final frames = objectCreationTrace.split('\n'); 48 | for (final frame in frames) { 49 | if (_startFrame.hasMatch(frame)) { 50 | return false; 51 | } 52 | if (_testHelperFrame.hasMatch(frame)) { 53 | if (exceptions.any((exception) => exception.hasMatch(frame)) || 54 | _exceptions.hasMatch(frame)) { 55 | continue; 56 | } 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | /// Postponed detector of test helpers. 64 | /// 65 | /// It is used to detect if the leak was created by a test helper, 66 | /// only if the leak is detected. 67 | /// 68 | /// It is needed because the detection is a heavy operation 69 | /// and should not be done for every tracked object. 70 | class CreationChecker { 71 | /// Creates instance of [CreationChecker]. 72 | /// 73 | /// Stack frames in [creationStack] that match any of [exceptions] 74 | /// will be ignored. 75 | CreationChecker( 76 | {required StackTrace creationStack, required List exceptions}) 77 | : _creationStack = creationStack, 78 | _exceptions = exceptions; 79 | StackTrace? _creationStack; 80 | List? _exceptions; 81 | 82 | /// True, if the leak was created by a test helper. 83 | /// 84 | /// This value is cached. The first calculation of the value 85 | /// is performance heavy. 86 | late final bool createdByTestHelpers = () { 87 | final result = isCreatedByTestHelper( 88 | _creationStack!.toString(), 89 | _exceptions!, 90 | ); 91 | // Nulling the references to make the object eligible for GC. 92 | _creationStack = null; 93 | _exceptions = null; 94 | return result; 95 | }(); 96 | } 97 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/shared/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | _formatting.dart-->_primitives.dart; 9 | _formatting.dart-->_util.dart; 10 | shared_model.dart-->_formatting.dart; 11 | shared_model.dart-->_primitives.dart; 12 | ``` 13 | 14 | ### Inversions 15 | In this folder: 0 16 | 17 | Including sub-folders: 0 18 | 19 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/shared/_formatting.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:vm_service/vm_service.dart'; 6 | 7 | import '_primitives.dart'; 8 | import '_util.dart'; 9 | 10 | /// Converts item in leak tracking context to string. 11 | String contextToString(Object? object) { 12 | return switch (object) { 13 | StackTrace() => _formatStackTrace(object), 14 | RetainingPath() => retainingPathToString(object), 15 | _ => object.toString(), 16 | }; 17 | } 18 | 19 | String _formatStackTrace(StackTrace stackTrace) { 20 | var result = stackTrace.toString(); 21 | 22 | result = removeLeakTrackingLines(result); 23 | 24 | // Remove spaces. 25 | // Spaces need to be removed from stacktrace 26 | // because otherwise test framework changes formatting 27 | // of a message from matcher. 28 | result = result.replaceAll(' ', '_'); 29 | 30 | return result; 31 | } 32 | 33 | /// Removes top lines that relate to leak_tracker. 34 | String removeLeakTrackingLines(String stackTrace) { 35 | final lines = stackTrace.split('\n'); 36 | var firstUserCode = 0; 37 | while (firstUserCode < lines.length && 38 | lines[firstUserCode].contains(leakTrackerStackTraceFragment)) { 39 | firstUserCode++; 40 | } 41 | lines.removeRange(0, firstUserCode); 42 | return lines.join('\n'); 43 | } 44 | 45 | String retainingPathToString(RetainingPath retainingPath) { 46 | final buffer = StringBuffer(); 47 | buffer.writeln( 48 | 'References that retain the object from garbage collection.', 49 | ); 50 | for (final item in retainingPath.elements?.reversed ?? []) { 51 | buffer.writeln(_retainingObjectToString(item)); 52 | } 53 | return buffer.toString(); 54 | } 55 | 56 | /// Properties of [RetainingObject] that are needed in the object's formatting. 57 | enum RetainingObjectProperty { 58 | lib([ 59 | ['value', 'class', 'library', 'name'], 60 | ['value', 'class', 'library', 'uri'], 61 | ['value', 'declaredType', 'class', 'library', 'name'], 62 | ['value', 'declaredType', 'class', 'library', 'uri'], 63 | ]), 64 | type([ 65 | ['value', 'class', 'name'], 66 | ['value', 'declaredType', 'class', 'name'], 67 | ['value', 'type'], 68 | ]), 69 | closureOwner([ 70 | ['value', 'closureFunction', 'owner', 'name'], 71 | ]), 72 | globalVarUri([ 73 | ['value', 'location', 'script', 'uri'], 74 | ]), 75 | globalVarName([ 76 | ['value', 'name'], 77 | ]); 78 | 79 | const RetainingObjectProperty(this.paths); 80 | 81 | /// Itemizes possible paths in [RetainingObject.toJson] to 82 | /// get the value of a property. 83 | final List> paths; 84 | } 85 | 86 | String _retainingObjectToString(RetainingObject object) { 87 | final json = object.toJson(); 88 | 89 | var result = property(RetainingObjectProperty.type, json) ?? ''; 90 | 91 | if (result == '_Closure') { 92 | final func = property(RetainingObjectProperty.closureOwner, json); 93 | if (func != null) { 94 | result = '$result (in $func)'; 95 | } 96 | } 97 | 98 | final lib = property(RetainingObjectProperty.lib, json); 99 | if (lib != null) { 100 | result = '$lib/$result'; 101 | } 102 | 103 | final location = 104 | object.parentField ?? object.parentMapKey ?? object.parentListIndex; 105 | 106 | if (location != null) { 107 | result = '$result:$location'; 108 | } 109 | 110 | if (result == 'dart.core/_Type') { 111 | final globalVarUri = property(RetainingObjectProperty.globalVarUri, json); 112 | final globalVarName = property(RetainingObjectProperty.globalVarName, json); 113 | result = '$globalVarUri/$globalVarName'; 114 | } 115 | 116 | return result; 117 | } 118 | 119 | String? property( 120 | RetainingObjectProperty property, 121 | Map json, 122 | ) { 123 | for (final path in property.paths) { 124 | final value = _valueByPath(json, path); 125 | if (!value.isNullOrEmpty) { 126 | return value; 127 | } 128 | } 129 | return null; 130 | } 131 | 132 | String? _valueByPath(Map json, List path) { 133 | var parent = json; 134 | for (final key in path.sublist(0, path.length - 1)) { 135 | final child = parent[key]; 136 | if (child is Map) { 137 | parent = child; 138 | } else { 139 | return null; 140 | } 141 | } 142 | 143 | // [path.last] contains the key for actual value. 144 | final value = parent[path.last]; 145 | 146 | return value?.toString(); 147 | } 148 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/shared/_primitives.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Result of [identityHashCode]. 6 | typedef IdentityHashCode = int; 7 | 8 | /// Finalizer builder to mock standard [identityHashCode]. 9 | typedef IdentityHashCoder = IdentityHashCode Function(Object object); 10 | 11 | int standardIdentityHashCoder(Object object) => identityHashCode(object); 12 | 13 | /// Fragment to detect leak_tracker invocation in callstack. 14 | const leakTrackerStackTraceFragment = '(package:leak_tracker/'; 15 | 16 | String fullClassName({ 17 | required String library, 18 | required String shortClassName, 19 | }) => 20 | '$library/$shortClassName'; 21 | 22 | class ObjectRef { 23 | ObjectRef(this.value); 24 | T value; 25 | } 26 | 27 | /// Customized link to documentation on how to troubleshoot leaks. 28 | /// 29 | /// Used to provide a link to the user in the generated leak report. 30 | /// Defaults to [Links.gitHubTroubleshooting]. 31 | String documentationLinkToUse = Links.gitHubTroubleshooting.value; 32 | 33 | String leakTrackerYamlHeader() => ''' 34 | # The text is generated by leak_tracker. 35 | # For leak troubleshooting tips open: 36 | # $documentationLinkToUse 37 | '''; 38 | 39 | /// Some links used in the package. 40 | /// 41 | /// The enum is test covered to catch broken links. 42 | enum Links { 43 | gitHubTroubleshooting( 44 | 'https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/TROUBLESHOOT.md', 45 | null, 46 | ), 47 | ; 48 | 49 | const Links(this.url, this.hash); 50 | 51 | final String url; 52 | final String? hash; 53 | String get value { 54 | if (hash == null) return url; 55 | return '$url#$hash'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/lib/src/shared/_util.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | /// This function is better than `as`, 6 | /// because `as` does not provide callstack on failure. 7 | T cast(Object? value) { 8 | if (value is T) return value; 9 | throw ArgumentError( 10 | '$value is of type ${value.runtimeType} that is not a subtype of $T', 11 | ); 12 | } 13 | 14 | extension IterableExtensions on Iterable { 15 | /// Returns the item or null, assuming that 16 | /// the length of the iterable is 0 or 1. 17 | // The name is consistent with other method names on iterables like 18 | // `firstOrNull, lastOrNull, and singleOrNull`. 19 | T? get onlyOrNull { 20 | if (length > 1) throw StateError('Length should not be more than one.'); 21 | return firstOrNull; 22 | } 23 | } 24 | 25 | void printToConsole(Object message) { 26 | // ignore: avoid_print, dart:io is not available in web 27 | print('leak_tracker: $message'); 28 | } 29 | 30 | extension SizeConversion on int { 31 | int get mbToBytes => this * 1024 * 1024; 32 | } 33 | 34 | extension StringChecks on String? { 35 | bool get isNullOrEmpty => this == null || this == ''; 36 | } 37 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leak_tracker 2 | version: 11.0.1 3 | description: A framework for memory leak tracking for Dart and Flutter applications. 4 | repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker 5 | 6 | environment: 7 | sdk: ^3.2.0 8 | 9 | # All dependencies here will become Flutter transitive dependencies. 10 | # Follow the process when adding: 11 | # https://github.com/flutter/flutter/blob/2be9b613099250cf8e84e1498a7624d9d2e04023/dev/bots/allowlist.dart#L13 12 | dependencies: 13 | clock: ^1.1.1 14 | collection: ^1.15.0 15 | meta: ^1.8.0 16 | path: ^1.8.3 17 | vm_service: '>=11.10.0 <16.0.0' 18 | 19 | dev_dependencies: 20 | dart_flutter_team_lints: ^3.1.0 21 | layerlens: ^1.0.16 22 | test: ^1.16.0 23 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/test_infra/data/dart_classes.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | 7 | class Named { 8 | Named(this.name); 9 | 10 | final String name; 11 | } 12 | 13 | class LeakTrackedClass { 14 | LeakTrackedClass() { 15 | LeakTracking.dispatchObjectCreated( 16 | library: library, 17 | className: '$LeakTrackedClass', 18 | object: this, 19 | ); 20 | } 21 | 22 | static const library = 'package:my_package/lib/src/my_lib.dart'; 23 | 24 | void dispose() { 25 | LeakTracking.dispatchObjectDisposed(object: this); 26 | } 27 | } 28 | 29 | final _notGCedObjects = []; 30 | 31 | class LeakingClass { 32 | LeakingClass() { 33 | // Not gced: 34 | _notGCedObjects.add(LeakTrackedClass()..dispose()); 35 | 36 | // Not disposed: 37 | LeakTrackedClass(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/test_infra/mocks/mock_object_tracker.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | import 'package:leak_tracker/src/leak_tracking/_object_tracker.dart'; 7 | 8 | enum EventType { 9 | started, 10 | disposed, 11 | } 12 | 13 | class Event { 14 | Event(this.type, this.object, this.context, this.trackedClass); 15 | 16 | final EventType type; 17 | final Object object; 18 | final Map? context; 19 | final String? trackedClass; 20 | } 21 | 22 | class MockObjectTracker extends ObjectTracker { 23 | MockObjectTracker() 24 | : super( 25 | disposalTime: const Duration(milliseconds: 100), 26 | numberOfGcCycles: defaultNumberOfGcCycles, 27 | maxRequestsForRetainingPath: 0, 28 | ); 29 | 30 | final events = []; 31 | 32 | @override 33 | void startTracking( 34 | Object object, { 35 | required Map? context, 36 | required String trackedClass, 37 | required PhaseSettings phase, 38 | }) => 39 | events.add(Event(EventType.started, object, context, trackedClass)); 40 | 41 | @override 42 | void dispatchDisposal( 43 | Object object, { 44 | required Map? context, 45 | }) => 46 | events.add(Event(EventType.disposed, object, context, null)); 47 | } 48 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/test_infra/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | Future loadPageHtmlContent(String url) async { 10 | final request = await HttpClient().getUrl(Uri.parse(url)); 11 | final response = await request.close(); 12 | 13 | final completer = Completer(); 14 | final content = StringBuffer(); 15 | response.transform(utf8.decoder).listen( 16 | content.write, 17 | onDone: () => completer.complete(content.toString()), 18 | ); 19 | await completer.future; 20 | return content.toString(); 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/_gc_counter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | import 'package:leak_tracker/src/leak_tracking/primitives/_gc_counter.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | test('shouldObjectBeGced', () { 11 | final now = DateTime(2022); 12 | const gcNow = 1000; 13 | const timeToDispose = Duration(milliseconds: 100); 14 | 15 | bool shouldBeGced(int disposalGcCount, DateTime disposalTime) => 16 | shouldObjectBeGced( 17 | gcCountAtDisposal: disposalGcCount, 18 | timeAtDisposal: disposalTime, 19 | currentGcCount: gcNow, 20 | currentTime: now, 21 | disposalTime: timeToDispose, 22 | numberOfGcCycles: defaultNumberOfGcCycles, 23 | ); 24 | 25 | final forJustGcEd = shouldBeGced(gcNow, now); 26 | expect(forJustGcEd, isFalse); 27 | 28 | final forNotEnoughTime = shouldBeGced(gcNow - 100, now); 29 | expect(forNotEnoughTime, isFalse); 30 | 31 | final forNotEnoughGc = 32 | shouldBeGced(gcNow, now.add(const Duration(days: -1))); 33 | expect(forNotEnoughGc, isFalse); 34 | 35 | final forEnoughTimeAndGc = shouldBeGced( 36 | gcNow - defaultNumberOfGcCycles, 37 | now.subtract(timeToDispose), 38 | ); 39 | expect(forEnoughTimeAndGc, isTrue); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/_leak_filter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | import 'package:leak_tracker/src/leak_tracking/_leak_filter.dart'; 7 | import 'package:leak_tracker/src/leak_tracking/_object_record.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | ObjectRecord _arrayRecord(PhaseSettings phase) => 11 | ObjectRecord([], {}, '', phase); 12 | ObjectRecord _dateTimeRecord(PhaseSettings phase) => 13 | ObjectRecord(DateTime.now(), {}, '', phase); 14 | 15 | void main() { 16 | test('All leaks are reported with default settings.', () { 17 | final filter = LeakFilter(); 18 | final record = _arrayRecord(const PhaseSettings.experimentalNotGCedOn()); 19 | 20 | expect(filter.shouldReport(LeakType.notDisposed, record), true); 21 | expect(filter.shouldReport(LeakType.notGCed, record), true); 22 | expect(filter.shouldReport(LeakType.gcedLate, record), true); 23 | }); 24 | 25 | test('$LeakFilter ignores all notDisposed.', () { 26 | final filter = LeakFilter(); 27 | final record = _arrayRecord( 28 | const PhaseSettings( 29 | ignoredLeaks: IgnoredLeaks( 30 | notDisposed: IgnoredLeaksSet.ignore(), 31 | experimentalNotGCed: IgnoredLeaksSet(), 32 | ), 33 | ), 34 | ); 35 | 36 | expect(filter.shouldReport(LeakType.notDisposed, record), false); 37 | expect(filter.shouldReport(LeakType.notGCed, record), true); 38 | expect(filter.shouldReport(LeakType.gcedLate, record), true); 39 | }); 40 | 41 | test('$LeakFilter ignores all notGCed.', () { 42 | final filter = LeakFilter(); 43 | final record = _arrayRecord( 44 | const PhaseSettings( 45 | ignoredLeaks: 46 | IgnoredLeaks(experimentalNotGCed: IgnoredLeaksSet.ignore()), 47 | ), 48 | ); 49 | 50 | expect(filter.shouldReport(LeakType.notDisposed, record), true); 51 | expect(filter.shouldReport(LeakType.notGCed, record), false); 52 | expect(filter.shouldReport(LeakType.gcedLate, record), false); 53 | }); 54 | 55 | test('$LeakFilter ignores a notGCed class.', () { 56 | final filter = LeakFilter(); 57 | const phase = PhaseSettings( 58 | ignoredLeaks: IgnoredLeaks( 59 | experimentalNotGCed: IgnoredLeaksSet.byClass({'List': null}), 60 | ), 61 | ); 62 | final arrayRecord = _arrayRecord(phase); 63 | final dateTimeRecord = _dateTimeRecord(phase); 64 | 65 | expect(filter.shouldReport(LeakType.notDisposed, arrayRecord), true); 66 | expect(filter.shouldReport(LeakType.notGCed, arrayRecord), false); 67 | expect(filter.shouldReport(LeakType.gcedLate, arrayRecord), false); 68 | 69 | expect(filter.shouldReport(LeakType.notDisposed, dateTimeRecord), true); 70 | expect(filter.shouldReport(LeakType.notGCed, dateTimeRecord), true); 71 | expect(filter.shouldReport(LeakType.gcedLate, dateTimeRecord), true); 72 | }); 73 | 74 | test('$LeakFilter ignored a notDisposed class.', () { 75 | final filter = LeakFilter(); 76 | const phase = PhaseSettings( 77 | ignoredLeaks: IgnoredLeaks( 78 | notDisposed: IgnoredLeaksSet.byClass({'List': null}), 79 | experimentalNotGCed: IgnoredLeaksSet()), 80 | ); 81 | final arrayRecord = _arrayRecord(phase); 82 | final dateTimeRecord = _dateTimeRecord(phase); 83 | 84 | expect(filter.shouldReport(LeakType.notDisposed, arrayRecord), false); 85 | expect(filter.shouldReport(LeakType.notGCed, arrayRecord), true); 86 | expect(filter.shouldReport(LeakType.gcedLate, arrayRecord), true); 87 | expect(filter.shouldReport(LeakType.notDisposed, dateTimeRecord), true); 88 | expect(filter.shouldReport(LeakType.notGCed, dateTimeRecord), true); 89 | expect(filter.shouldReport(LeakType.gcedLate, dateTimeRecord), true); 90 | }); 91 | 92 | test('$LeakFilter respects limit.', () { 93 | final filter = LeakFilter(); 94 | const phase = PhaseSettings( 95 | ignoredLeaks: IgnoredLeaks( 96 | notDisposed: IgnoredLeaksSet.byClass({'List': 2}), 97 | ), 98 | ); 99 | final arrayRecord = _arrayRecord(phase); 100 | final dateTimeRecord = _dateTimeRecord(phase); 101 | 102 | expect(filter.shouldReport(LeakType.notDisposed, dateTimeRecord), true); 103 | expect(filter.shouldReport(LeakType.notDisposed, arrayRecord), false); 104 | expect(filter.shouldReport(LeakType.notDisposed, arrayRecord), false); 105 | expect(filter.shouldReport(LeakType.notDisposed, arrayRecord), true); 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/_object_record_set_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/src/leak_tracking/_object_record.dart'; 6 | import 'package:leak_tracker/src/leak_tracking/_object_record_set.dart'; 7 | import 'package:leak_tracker/src/leak_tracking/primitives/model.dart'; 8 | import 'package:leak_tracker/src/shared/_primitives.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | bool _tick = true; 12 | 13 | final _coders = { 14 | 'real': standardIdentityHashCoder, 15 | 'alwaysTheSame': (object) => 1, 16 | 'alterative': (object) => (_tick = !_tick) ? 1 : 2, 17 | }; 18 | 19 | const _phase = PhaseSettings(); 20 | final _record = ObjectRecord([], {}, '', _phase); 21 | 22 | void main() { 23 | for (var coderName in _coders.keys) { 24 | test('$ObjectRecordSet works well with $coderName', () { 25 | final items = [ 26 | [0], 27 | [1], 28 | [2], 29 | ]; 30 | 31 | final theSet = ObjectRecordSet(coder: _coders[coderName]!); 32 | 33 | final records = items.map((i) => _addItemAndValidate(theSet, i)).toList(); 34 | 35 | expect(records[0], isNot(records[1])); 36 | expect(records[1], isNot(records[2])); 37 | expect(records[0], isNot(records[2])); 38 | 39 | for (var r in records) { 40 | _removeItemAndValidate(theSet, r); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | ObjectRecord _addItemAndValidate(ObjectRecordSet theSet, Object item) { 47 | final length = theSet.length; 48 | 49 | // Add first time. 50 | final (record: record1, wasAbsent: wasAbsent1) = 51 | theSet.putIfAbsent(item, {}, _phase, ''); 52 | expect(theSet.length, length + 1); 53 | expect(theSet.contains(record1), true); 54 | expect(theSet.contains(_record), false); 55 | expect(wasAbsent1, true); 56 | 57 | // Add second time. 58 | final (record: record2, wasAbsent: wasAbsent2) = 59 | theSet.putIfAbsent(item, {}, _phase, ''); 60 | expect(identical(record1, record2), true); 61 | expect(theSet.length, length + 1); 62 | expect(wasAbsent2, false); 63 | 64 | var count = 0; 65 | theSet.toIterable().forEach((record) => count++); 66 | expect(count, theSet.length); 67 | 68 | expect(theSet.record(item), record1); 69 | 70 | return record1; 71 | } 72 | 73 | void _removeItemAndValidate(ObjectRecordSet theSet, ObjectRecord record) { 74 | final length = theSet.length; 75 | 76 | expect(theSet.contains(record), true); 77 | expect(theSet.contains(_record), false); 78 | theSet.remove(record); 79 | expect(theSet.length, length - 1); 80 | expect(theSet.contains(record), false); 81 | expect(theSet.contains(_record), false); 82 | 83 | var count = 0; 84 | theSet.toIterable().forEach((record) => count++); 85 | expect(count, theSet.length); 86 | 87 | expect(theSet.record(record.ref.target!), null); 88 | } 89 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/dart_developer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:core'; 6 | import 'dart:developer'; 7 | 8 | import 'package:test/test.dart'; 9 | 10 | Future> _createTrackedObject( 11 | Finalizer finalizer, 12 | ) async { 13 | final theObject = Iterable.generate(1000, (_) => DateTime.now()); 14 | 15 | // Delay to increase chances for the object to get to old gc space. 16 | await Future.delayed(const Duration(milliseconds: 10)); 17 | 18 | finalizer.attach(theObject, identityHashCode(theObject)); 19 | return WeakReference(theObject); 20 | } 21 | 22 | late List> _storage; 23 | 24 | void _allocateMemory() { 25 | _storage.add(Iterable.generate(10000, (_) => DateTime.now()).toList()); 26 | } 27 | 28 | /// Tests for non-guaranteed assumptions about Dart garbage collector, 29 | /// the leak_tracker relies on. 30 | void main() { 31 | setUp(() => _storage = []); 32 | 33 | tearDown(() => _storage.clear()); 34 | 35 | test('Non-referenced object is finalized and gced after barrier increase.', 36 | () async { 37 | var finalized = false; 38 | final finalizer = Finalizer((token) => finalized = true); 39 | final ref = await _createTrackedObject(finalizer); 40 | final barrier = reachabilityBarrier; 41 | 42 | while (reachabilityBarrier <= barrier + 2) { 43 | _allocateMemory(); 44 | // Delay to give space to garbage collector. 45 | await Future.delayed(const Duration()); 46 | } 47 | 48 | expect(finalized, true); 49 | expect(ref.target, isNull); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/helpers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | test('formattedRetainingPath returns expected path', () async { 11 | final myObject = [1, 2, 3]; 12 | final path = await formattedRetainingPath(WeakReference(myObject)); 13 | expect(path, contains('dart.core/_GrowableList')); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/model_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | 7 | import 'package:leak_tracker/src/leak_tracking/primitives/model.dart'; 8 | import 'package:leak_tracker/src/shared/shared_model.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | test('$Leaks serializes.', () { 13 | final leaks = Leaks({ 14 | LeakType.gcedLate: [ 15 | LeakReport( 16 | trackedClass: 'trackedClass1', 17 | type: 't1', 18 | context: {'a': 1, 'b': 3.14, 'c': ''}, 19 | code: 1, 20 | phase: null, 21 | ), 22 | ], 23 | LeakType.notDisposed: [ 24 | LeakReport( 25 | trackedClass: 'trackedClass2', 26 | type: 't2', 27 | context: {}, 28 | code: 2, 29 | phase: null, 30 | ), 31 | ], 32 | LeakType.notGCed: [ 33 | LeakReport( 34 | trackedClass: 'trackedClass3', 35 | type: 't3', 36 | context: {}, 37 | code: 1, 38 | phase: null, 39 | ), 40 | ], 41 | }); 42 | 43 | final json = leaks.toJson(); 44 | 45 | expect( 46 | jsonEncode(json), 47 | jsonEncode(Leaks.fromJson(json).toJson()), 48 | ); 49 | }); 50 | 51 | test('$LeakSummary serializes.', () { 52 | final leakSummary = LeakSummary( 53 | { 54 | LeakType.gcedLate: 2, 55 | LeakType.notDisposed: 3, 56 | LeakType.notGCed: 4, 57 | }, 58 | time: DateTime(2022), 59 | ); 60 | 61 | final json = leakSummary.toJson(); 62 | 63 | expect( 64 | jsonEncode(json), 65 | jsonEncode(LeakSummary.fromJson(json).toJson()), 66 | ); 67 | }); 68 | 69 | test('$PhaseSettings equality', () { 70 | const phase1 = PhaseSettings(); 71 | 72 | const phase2 = PhaseSettings( 73 | leakDiagnosticConfig: LeakDiagnosticConfig( 74 | collectStackTraceOnDisposal: true, 75 | collectStackTraceOnStart: true, 76 | ), 77 | ); 78 | 79 | expect(phase1 == phase2, false); 80 | }); 81 | 82 | group('$ValueSampler', () { 83 | test('equality', () { 84 | final sampler1 = ValueSampler.start(initialValue: 1); 85 | sampler1.add(2); 86 | sampler1.add(3); 87 | 88 | final sampler2 = ValueSampler.start(initialValue: 1); 89 | sampler2.add(3); 90 | sampler2.add(2); 91 | 92 | expect(sampler1 == sampler2, true); 93 | }); 94 | 95 | test('math', () { 96 | final sampler = ValueSampler.start(initialValue: 1); 97 | 98 | expect(sampler.samples, 1); 99 | expect(sampler.absAvg, 1); 100 | expect(sampler.absMax, 1); 101 | expect(sampler.deltaAvg, 0); 102 | expect(sampler.deltaMax, 0); 103 | 104 | sampler.add(2); 105 | 106 | expect(sampler.samples, 2); 107 | expect(sampler.absAvg, 1.5); 108 | expect(sampler.absMax, 2); 109 | expect(sampler.deltaAvg, 0.5); 110 | expect(sampler.deltaMax, 1); 111 | }); 112 | }); 113 | 114 | group('$IgnoredLeaksSet', () { 115 | test('merges', () { 116 | const list1 = IgnoredLeaksSet.byClass({'class1': null}); 117 | const list2 = IgnoredLeaksSet.byClass({'class2': null}); 118 | 119 | final result = list1.merge(list2); 120 | 121 | expect(result.isIgnored('class1'), true); 122 | expect(result.isIgnored('class2'), true); 123 | }); 124 | 125 | test('removes', () { 126 | const list = IgnoredLeaksSet.byClass({'class1': null, 'class2': null}); 127 | 128 | final result = list.track(['class1']); 129 | 130 | expect(result.isIgnored('class1'), false); 131 | expect(result.isIgnored('class2'), true); 132 | }); 133 | }); 134 | 135 | group('$IgnoredLeaks', () { 136 | group('equals', () { 137 | test('trivial', () { 138 | const list1 = IgnoredLeaks(); 139 | const list2 = IgnoredLeaks(); 140 | 141 | expect(list1 == list2, true); 142 | }); 143 | 144 | test('ignored equal', () { 145 | const list1 = IgnoredLeaks( 146 | notDisposed: 147 | IgnoredLeaksSet(byClass: {'MyClass1': null, 'MyClass2': null}), 148 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 149 | ); 150 | const list2 = IgnoredLeaks( 151 | notDisposed: 152 | IgnoredLeaksSet(byClass: {'MyClass2': null, 'MyClass1': null}), 153 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 154 | ); 155 | expect(list1 == list2, true); 156 | }); 157 | 158 | test('different', () { 159 | const list1 = IgnoredLeaks( 160 | notDisposed: IgnoredLeaksSet(byClass: {'MyClass': null}), 161 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 162 | ); 163 | const list2 = IgnoredLeaks( 164 | notDisposed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 165 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 166 | ); 167 | expect(list1 == list2, false); 168 | }); 169 | }); 170 | }); 171 | } 172 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/orchestration_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:leak_tracker/src/leak_tracking/helpers.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | group('forceGC', () { 12 | test('forces gc', () async { 13 | Object? myObject = [1, 2, 3, 4, 5]; 14 | final ref = WeakReference(myObject); 15 | myObject = null; 16 | 17 | await forceGC(); 18 | 19 | expect(ref.target, null); 20 | }); 21 | 22 | test('times out', () async { 23 | await expectLater( 24 | () async => forceGC(timeout: Duration.zero), 25 | throwsA(isA()), 26 | ); 27 | }); 28 | 29 | test('takes reasonable time', () async { 30 | const rounds = 100; 31 | final sw = Stopwatch()..start(); 32 | 33 | for (var _ in Iterable.generate(rounds)) { 34 | await forceGC(); 35 | } 36 | 37 | final durationPerRound = sw.elapsed ~/ rounds; 38 | expect(durationPerRound.inMilliseconds, lessThan(200)); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/leak_tracking/retaining_path/_retaining_path_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:leak_tracker/src/leak_tracking/primitives/_retaining_path/_connection.dart'; 8 | import 'package:leak_tracker/src/leak_tracking/primitives/_retaining_path/_retaining_path.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | class MyClass { 12 | MyClass(); 13 | } 14 | 15 | class MyArgClass { 16 | MyArgClass(); 17 | } 18 | 19 | void main() { 20 | test('Path for $MyClass instance is found.', () async { 21 | final instance = MyClass(); 22 | final connection = await connect(); 23 | 24 | final path = await retainingPath( 25 | connection, 26 | instance, 27 | ); 28 | 29 | expect(path!.elements, isNotEmpty); 30 | }); 31 | 32 | test('Path for class with generic arg is found.', () async { 33 | final instance = MyArgClass(); 34 | final connection = await connect(); 35 | 36 | final path = await retainingPath( 37 | connection, 38 | instance, 39 | ); 40 | expect(path!.elements, isNotEmpty); 41 | }); 42 | 43 | test('Connection can be reused', () async { 44 | final instance1 = MyClass(); 45 | final instance2 = MyClass(); 46 | final connection = await connect(); 47 | 48 | final obtainers = [ 49 | retainingPath(connection, instance1), 50 | retainingPath(connection, instance2), 51 | ]; 52 | 53 | final result = await Future.wait(obtainers); 54 | 55 | expect(result.where((p) => p == null), hasLength(0)); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /pkgs/leak_tracker/test/tests/shared/_primitives_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/src/shared/_primitives.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import '../../test_infra/utils.dart'; 9 | 10 | void main() { 11 | for (final link in Links.values) { 12 | test('$link is not broken', () async { 13 | final content = await loadPageHtmlContent(link.value); 14 | 15 | expect(content, isNot(contains('"title":"File not found"'))); 16 | 17 | final hash = link.hash; 18 | if (hash != null) { 19 | expect(content, contains('href="#$hash"')); 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.10 2 | 3 | * Upgrade leak_tracker to 11.0.1. 4 | 5 | ## 3.0.9 6 | 7 | * Upgrade leak_tracker to 10.0.8. 8 | 9 | 10 | ## 3.0.8 11 | 12 | * Upgrade leak_tracker to 10.0.7. 13 | 14 | ## 3.0.7 15 | 16 | * No changes. 17 | 18 | ## 3.0.6 19 | 20 | * Upgrade leak_tracker to 10.0.6. 21 | 22 | ## 3.0.5 23 | 24 | * Upgrade leak_tracker to 10.0.5. 25 | 26 | ## 3.0.4 27 | 28 | * Internal clean up. 29 | 30 | ## 3.0.3 31 | 32 | * Upgrade leak_tracker to 10.0.4. 33 | * Upgrade leak_tracker_testing to 3.0.1. 34 | 35 | ## 3.0.2 36 | 37 | * Upgrade leak_tracker_testing to 3.0.0. 38 | 39 | ## 3.0.1 40 | 41 | * Upgrade leak_tracker_testing to 2.0.3. 42 | 43 | ## 3.0.0 44 | 45 | * Upgrade leak_tracker to 10.0.3. 46 | 47 | ## 2.0.4 48 | 49 | * Require Dart SDK 3.2.0 or later. 50 | 51 | ## 2.0.3 52 | 53 | * Allow to ignore objects created by test helpers. 54 | * Upgrade to leak_tracker 10.0.1 and leak_tracker_testing 2.0.2. 55 | 56 | ## 2.0.2 57 | 58 | * Replaced deprecated `MemoryAllocations` with `FlutterMemoryAllocations`. 59 | 60 | ## 2.0.1 61 | 62 | * Upgrade to leak_tracker 10.0.0 and leak_tracker_testing 2.0.1. 63 | 64 | ## 2.0.0 65 | 66 | * Remove declaration of testWidgetsWithLeakTracking. 67 | 68 | ## 1.0.12 69 | 70 | * Update to use `package:lints/recommended.yaml` for analysis. 71 | * Add API to integrate with testWidgets. 72 | 73 | ## 1.0.10 74 | 75 | * Move LeakTesting out of this package to leak_tracker. 76 | * Fix bug in equality for LeakTracking. 77 | 78 | ## 1.0.9 79 | 80 | * Update `testWidgetsWithLeakTracking` to avoid duplicated leak tracking by testWidgets. 81 | 82 | ## 1.0.8 83 | 84 | * Make configuration adjustable. 85 | 86 | ## 1.0.7 87 | 88 | * Set version of leak_tracker_testing to `^1.0.5`. 89 | 90 | ## 1.0.6 91 | 92 | * If an object is not disposed by the end of testing, mark it as notDisposed. 93 | 94 | ## 1.0.5 95 | 96 | * Bump version of SDK to 3.1.2. 97 | 98 | ## 1.0.4 99 | 100 | * Update matcher for memory events to handle async callbacks. 101 | 102 | ## 1.0.3 103 | 104 | * Define matcher to verify if a class is reporting memory allocations. 105 | 106 | ## 1.0.2 107 | 108 | * Add debugging constructors to LeakTrackingTestConfig, per leak type. 109 | 110 | ## 1.0.1 111 | 112 | * Expose global leak tracking settings. 113 | 114 | ## 1.0.0 115 | 116 | * First release. 117 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/leak_tracker_flutter_testing.svg)](https://pub.dev/packages/leak_tracker_flutter_testing) 2 | [![package publisher](https://img.shields.io/pub/publisher/leak_tracker_flutter_testing.svg)](https://pub.dev/packages/leak_tracker_flutter_testing/publisher) 3 | 4 | ## What is leak_tracker_flutter_testing? 5 | 6 | `leak_tracker_flutter_testing` is Flutter specific test helpers for [leak_tracker](https://pub.dev/packages/leak_tracker). 7 | 8 | They are separated from [leak_tracker_testing](https://pub.dev/packages/leak_tracker_testing) because the last one is pure Flutter 9 | package and should not reference Flutter Framework. 10 | 11 | Read more in [leak tracking overview](https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/OVERVIEW.md). 12 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | 8 | linter: 9 | rules: 10 | - avoid_print 11 | - sort_constructors_first 12 | - sort_unnamed_constructors_first 13 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | leak_tracker_flutter_testing.dart-->src; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/leak_tracker_flutter_testing.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'package:leak_tracker/leak_tracker.dart' 6 | show IgnoredLeaks, LeakReport, LeakTracking, LeakType, Leaks; 7 | export 'package:leak_tracker_testing/leak_tracker_testing.dart' 8 | show LeakTesting, isLeakFree; 9 | 10 | export 'src/matchers.dart'; 11 | export 'src/testing.dart'; 12 | export 'src/testing_for_testing/leaking_classes.dart'; 13 | export 'src/testing_for_testing/test_case.dart'; 14 | export 'src/testing_for_testing/test_settings.dart'; 15 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/matchers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:matcher/matcher.dart'; 9 | 10 | /// Invokes [callback] and collects 11 | /// events dispatched to [FlutterMemoryAllocations.instance] for [type]. 12 | Future> memoryEvents( 13 | FutureOr Function() callback, 14 | Type type, 15 | ) async { 16 | final events = []; 17 | 18 | void listener(ObjectEvent event) { 19 | if (event.object.runtimeType == type) { 20 | events.add(event); 21 | } 22 | } 23 | 24 | FlutterMemoryAllocations.instance.addListener(listener); 25 | await callback(); 26 | FlutterMemoryAllocations.instance.removeListener(listener); 27 | 28 | return events; 29 | } 30 | 31 | /// Checks if `Iterable` contains two events, 32 | /// first `ObjectCreated` and then `ObjectDisposed`. 33 | Matcher areCreateAndDispose = const _AreCreateAndDispose(); 34 | 35 | class _AreCreateAndDispose extends Matcher { 36 | const _AreCreateAndDispose(); 37 | 38 | static const _key = 'description'; 39 | 40 | @override 41 | bool matches(Object? item, Map matchState) { 42 | if (item is! Iterable) { 43 | matchState[_key] = 'The matcher applies to $Iterable<$ObjectEvent>.'; 44 | return false; 45 | } 46 | 47 | if (item.length == 2 && 48 | item.first is ObjectCreated && 49 | item.last is ObjectDisposed) { 50 | return true; 51 | } 52 | 53 | matchState[_key] = 'The events are expected to be first ' 54 | '$ObjectCreated and then $ObjectDisposed.\n' 55 | 'Instead, they are ${item.length} events:\n$item.'; 56 | 57 | return false; 58 | } 59 | 60 | @override 61 | Description describeMismatch( 62 | Object? item, 63 | Description mismatchDescription, 64 | Map matchState, 65 | bool verbose, 66 | ) { 67 | return mismatchDescription..add(matchState[_key] as String); 68 | } 69 | 70 | @override 71 | Description describe(Description description) => 72 | description.add('instrumented'); 73 | } 74 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/testing.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:leak_tracker/leak_tracker.dart'; 7 | import 'package:leak_tracker_testing/leak_tracker_testing.dart'; 8 | 9 | /// Makes sure leak tracking is set up for a test. 10 | /// 11 | /// If `settings.ignore` is true, the method is noop. 12 | /// If leak tracking is not started, starts it. 13 | /// Configures `LeakTracking.phase` to match [settings]. 14 | void maybeSetupLeakTrackingForTest( 15 | LeakTesting? settings, 16 | String testDescription, 17 | ) { 18 | if (!LeakTesting.enabled) return; 19 | 20 | final leakTesting = settings ?? LeakTesting.settings; 21 | if (leakTesting.ignore) return; 22 | 23 | _maybeStartLeakTracking(); 24 | 25 | final phase = PhaseSettings( 26 | name: testDescription, 27 | leakDiagnosticConfig: leakTesting.leakDiagnosticConfig, 28 | ignoredLeaks: leakTesting.ignoredLeaks, 29 | baselining: leakTesting.baselining, 30 | ignoreLeaks: leakTesting.ignore, 31 | ); 32 | 33 | LeakTracking.phase = phase; 34 | } 35 | 36 | /// If leak tracking is enabled, stops it and 37 | /// declares notDisposed objects as leaks. 38 | void maybeTearDownLeakTrackingForTest() { 39 | if (!LeakTesting.enabled || 40 | !LeakTracking.isStarted || 41 | LeakTracking.phase.ignoreLeaks) { 42 | return; 43 | } 44 | LeakTracking.phase = const PhaseSettings.ignored(); 45 | } 46 | 47 | /// Should be invoked after execution of all tests to report found leaks. 48 | /// 49 | /// Is noop if leak tracking is not started. 50 | Future maybeTearDownLeakTrackingForAll() async { 51 | if (!LeakTesting.enabled || !LeakTracking.isStarted) { 52 | // Reporter is invoked so that tests can verify the number of 53 | // collected leaks is as expected. 54 | LeakTesting.collectedLeaksReporter(Leaks({})); 55 | return; 56 | } 57 | 58 | // The listener is not added/removed for each test, 59 | // because GC may happen after test is complete. 60 | FlutterMemoryAllocations.instance 61 | .removeListener(_dispatchFlutterEventToLeakTracker); 62 | 63 | final notGCedTracked = 64 | !LeakTesting.settings.ignoredLeaks.experimentalNotGCed.ignoreAll; 65 | 66 | if (notGCedTracked) { 67 | await forceGC(fullGcCycles: defaultNumberOfGcCycles); 68 | } 69 | 70 | LeakTracking.declareNotDisposedObjectsAsLeaks(); 71 | final leaks = await LeakTracking.collectLeaks(); 72 | LeakTracking.stop(); 73 | 74 | LeakTesting.collectedLeaksReporter(leaks); 75 | } 76 | 77 | void _dispatchFlutterEventToLeakTracker(ObjectEvent event) { 78 | return LeakTracking.dispatchObjectEvent(event.toMap()); 79 | } 80 | 81 | /// Starts leak tracking with all leaks ignored. 82 | void _maybeStartLeakTracking() { 83 | if (LeakTracking.isStarted) return; 84 | 85 | LeakTracking.phase = const PhaseSettings.ignored(); 86 | LeakTracking.start(config: LeakTrackingConfig.passive()); 87 | FlutterMemoryAllocations.instance 88 | .addListener(_dispatchFlutterEventToLeakTracker); 89 | } 90 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/testing_for_testing/README.md: -------------------------------------------------------------------------------- 1 | This folder contains model and tests to test that methods like `testWidgets`, 2 | that use API of this package, detect leaks as expected. 3 | 4 | See example of usage in [the test](../../../test/tests/end_to_end/testing_test.dart). 5 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/testing_for_testing/leaking_classes.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:leak_tracker/leak_tracker.dart'; 7 | 8 | final _notGcedStorage = []; 9 | 10 | /// Example of stateless leaking widget. 11 | class StatelessLeakingWidget extends StatelessWidget { 12 | StatelessLeakingWidget({ 13 | super.key, 14 | this.notGCed = true, 15 | this.notDisposed = true, 16 | }) { 17 | if (notGCed) { 18 | _notGcedStorage.add(InstrumentedDisposable()..dispose()); 19 | } 20 | if (notDisposed) { 21 | // ignore: unused_local_variable 22 | final notDisposedObject = InstrumentedDisposable(); 23 | } 24 | } 25 | 26 | final bool notGCed; 27 | final bool notDisposed; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return const Placeholder(); 32 | } 33 | } 34 | 35 | /// Example of instrumented disposable. 36 | class InstrumentedDisposable { 37 | InstrumentedDisposable() { 38 | LeakTracking.dispatchObjectCreated( 39 | library: library, 40 | className: '$InstrumentedDisposable', 41 | object: this, 42 | ); 43 | } 44 | 45 | static const library = 'package:my_package/lib/src/my_lib.dart'; 46 | 47 | void dispose() { 48 | LeakTracking.dispatchObjectDisposed(object: this); 49 | } 50 | } 51 | 52 | final _notGCedObjects = []; 53 | 54 | /// Example of leaking class. 55 | class LeakingClass { 56 | LeakingClass() { 57 | _notGCedObjects.add(InstrumentedDisposable()..dispose()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/testing_for_testing/test_case.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:leak_tracker/leak_tracker.dart'; 8 | import 'package:leak_tracker_testing/leak_tracker_testing.dart'; 9 | import 'package:matcher/expect.dart'; 10 | 11 | /// Signature of `pumpWidget` method. 12 | typedef PumpWidgetsCallback = Future Function( 13 | Widget widget, [ 14 | Duration? duration, 15 | ]); 16 | 17 | /// Signature of `runAsync` method. 18 | typedef RunAsyncCallback = Future Function( 19 | Future Function() callback, 20 | ); 21 | 22 | /// Callback for test body, with access to Flutter specific test methods. 23 | typedef TestCallback = Future Function( 24 | PumpWidgetsCallback? pumpWidgets, 25 | RunAsyncCallback? runAsync, 26 | ); 27 | 28 | /// A test case to verify leak detection. 29 | class LeakTestCase { 30 | LeakTestCase({ 31 | required this.name, 32 | required this.body, 33 | this.notDisposedTotal = 0, 34 | this.notGCedTotal = 0, 35 | this.notDisposedInHelpers = 0, 36 | this.notGCedInHelpers = 0, 37 | }); 38 | 39 | /// Name of the test. 40 | final String name; 41 | 42 | /// Test body. 43 | final TestCallback body; 44 | 45 | /// Expected number of not disposed objects. 46 | final int notDisposedTotal; 47 | 48 | /// Expected number of not GCed objects. 49 | final int notGCedTotal; 50 | 51 | /// Expected number of not disposed objects created by test helpers. 52 | final int notDisposedInHelpers; 53 | 54 | /// Expected number of not GCed objects created by test helpers. 55 | final int notGCedInHelpers; 56 | 57 | /// Verifies [leaks] contain expected leaks for the test. 58 | /// 59 | /// [settings] is used to determine: 60 | /// * if some leaks should be ignored 61 | /// * which diagnostics should be collected 62 | /// 63 | /// [testDescription] is used in description for the failed expectations. 64 | void verifyLeaks(Leaks leaks, LeakTesting settings, 65 | {required String testDescription}) { 66 | final expectedContextKeys = [ 67 | if (settings.leakDiagnosticConfig.collectStackTraceOnStart) 68 | ContextKeys.startCallstack, 69 | ]; 70 | 71 | _verifyLeakList( 72 | testDescription, 73 | LeakType.notDisposed, 74 | leaks, 75 | ignore: settings.ignore || settings.ignoredLeaks.notDisposed.ignoreAll, 76 | expectedCount: notDisposedTotal - 77 | (settings.ignoredLeaks.createdByTestHelpers 78 | ? notDisposedInHelpers 79 | : 0), 80 | expectedContextKeys: expectedContextKeys, 81 | ); 82 | 83 | // Add diagnostics that is relevant for notGCed only. 84 | if (settings.leakDiagnosticConfig.collectRetainingPathForNotGCed) { 85 | expectedContextKeys.add(ContextKeys.retainingPath); 86 | } 87 | if (settings.leakDiagnosticConfig.collectStackTraceOnDisposal) { 88 | expectedContextKeys.add(ContextKeys.disposalCallstack); 89 | } 90 | 91 | _verifyLeakList( 92 | testDescription, 93 | LeakType.notGCed, 94 | leaks, 95 | ignore: settings.ignore || 96 | settings.ignoredLeaks.experimentalNotGCed.ignoreAll, 97 | expectedCount: notGCedTotal - 98 | (settings.ignoredLeaks.createdByTestHelpers ? notGCedInHelpers : 0), 99 | expectedContextKeys: expectedContextKeys, 100 | ); 101 | } 102 | 103 | void _verifyLeakList( 104 | String testDescription, 105 | LeakType type, 106 | Leaks leaks, { 107 | required int expectedCount, 108 | required List expectedContextKeys, 109 | required bool ignore, 110 | }) { 111 | final list = leaks.byType[type] ?? []; 112 | 113 | expect( 114 | list.length, 115 | ignore ? 0 : expectedCount, 116 | reason: '$testDescription, $type, ignore: $ignore', 117 | ); 118 | 119 | // Verify context keys. 120 | for (final leak in list) { 121 | final actualKeys = leak.context?.keys.toList() ?? []; 122 | expect(actualKeys..sort(), equals(expectedContextKeys..sort()), 123 | reason: '$testDescription, $type'); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/lib/src/testing_for_testing/test_settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker_testing/leak_tracker_testing.dart'; 6 | 7 | LeakTesting _trackingOn(LeakTesting settings) { 8 | final result = settings 9 | .withTrackedAll() 10 | .withTracked(allNotDisposed: true, experimentalAllNotGCed: true); 11 | return result; 12 | } 13 | 14 | /// Test cases for leak detection settings. 15 | final Map 16 | leakTestingSettingsCases = { 17 | 'tracking on': _trackingOn, 18 | 'tracking off': (s) => _trackingOn(s).withIgnoredAll(), 19 | 'notGCed off': (s) => _trackingOn(s).withIgnored(allNotGCed: true), 20 | 'notDisposed off': (s) => _trackingOn(s).withIgnored(allNotDisposed: true), 21 | 'testHelpers off': (s) => 22 | _trackingOn(s).withIgnored(createdByTestHelpers: true), 23 | 'testHelpers on': (s) => 24 | _trackingOn(s).withTracked(createdByTestHelpers: true), 25 | 'creation trace': (s) => _trackingOn(s).withCreationStackTrace(), 26 | 'disposal trace': (s) => _trackingOn(s).withDisposalStackTrace(), 27 | 'retaining path': (s) => _trackingOn(s).withRetainingPath(), 28 | 'all diagnostics': (s) => _trackingOn(s) 29 | .withCreationStackTrace() 30 | .withDisposalStackTrace() 31 | .withRetainingPath(), 32 | }; 33 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leak_tracker_flutter_testing 2 | version: 3.0.10 3 | description: An internal package to test leak tracking with Flutter. 4 | repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_flutter_testing 5 | 6 | environment: 7 | sdk: ^3.2.0 8 | flutter: '>=3.18.0-18.0.pre.54' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | leak_tracker: '>=11.0.1 <12.0.0' 14 | leak_tracker_testing: '>=3.0.1 <4.0.0' 15 | matcher: ^0.12.16 16 | meta: ^1.8.0 17 | 18 | dev_dependencies: 19 | dart_flutter_team_lints: ^3.1.0 20 | layerlens: ^1.0.16 21 | test: ^1.25.0 22 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | leak_tracker: 3 | path: ../leak_tracker 4 | leak_tracker_testing: 5 | path: ../leak_tracker_testing 6 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/test_infra/event_tracker.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | enum EventType { 6 | started, 7 | disposed, 8 | } 9 | 10 | class Event { 11 | Event(this.type, this.object, this.context, this.library, this.className); 12 | 13 | final EventType type; 14 | final Object object; 15 | final Map? context; 16 | final String? library; 17 | final String? className; 18 | } 19 | 20 | class EventTracker { 21 | EventTracker(); 22 | 23 | final events = []; 24 | 25 | void dispatchObjectCreated({ 26 | required String library, 27 | required String className, 28 | required Object object, 29 | Map? context, 30 | }) { 31 | events.add(Event(EventType.started, object, context, library, className)); 32 | } 33 | 34 | void dispatchObjectDisposed({ 35 | required Object object, 36 | Map? context, 37 | }) { 38 | events.add(Event(EventType.disposed, object, context, null, null)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/test_infra/memory_leak_tests.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 8 | 9 | import 'test_helpers.dart'; 10 | 11 | /// Objects that should not be GCed during. 12 | final _retainer = []; 13 | 14 | /// Test cases for memory leaks. 15 | /// 16 | /// They are separate from test execution to allow 17 | /// excluding them from test helpers. 18 | final List memoryLeakTests = [ 19 | LeakTestCase( 20 | name: 'no leaks', 21 | body: (PumpWidgetsCallback? pumpWidgets, 22 | RunAsyncCallback? runAsync) async { 23 | Container(); 24 | }, 25 | ), 26 | LeakTestCase( 27 | name: 'not disposed disposable', 28 | body: (PumpWidgetsCallback? pumpWidgets, 29 | RunAsyncCallback? runAsync) async { 30 | InstrumentedDisposable(); 31 | }, 32 | notDisposedTotal: 1, 33 | ), 34 | LeakTestCase( 35 | name: 'not GCed disposable', 36 | body: (PumpWidgetsCallback? pumpWidgets, 37 | RunAsyncCallback? runAsync) async { 38 | _retainer.add(InstrumentedDisposable()..dispose()); 39 | }, 40 | notGCedTotal: 1, 41 | ), 42 | LeakTestCase( 43 | name: 'leaking widget', 44 | body: (PumpWidgetsCallback? pumpWidgets, 45 | RunAsyncCallback? runAsync) async { 46 | StatelessLeakingWidget(); 47 | }, 48 | notDisposedTotal: 1, 49 | notGCedTotal: 1, 50 | ), 51 | LeakTestCase( 52 | name: 'leaks from test helpers', 53 | body: (PumpWidgetsCallback? pumpWidgets, 54 | RunAsyncCallback? runAsync) async { 55 | createLeakingWidget(); 56 | }, 57 | notDisposedTotal: 1, 58 | notGCedTotal: 1, 59 | notDisposedInHelpers: 1, 60 | notGCedInHelpers: 1, 61 | ), 62 | ]; 63 | 64 | String memoryLeakTestsFilePath() { 65 | return RegExp(r'(\/[^\/]*.dart):') 66 | .firstMatch(StackTrace.current.toString())! 67 | .group(1) 68 | .toString(); 69 | } 70 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/test_infra/test_helpers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 6 | 7 | /// Test only function that creates a leaking object. 8 | /// 9 | /// If test helpers are ignored in leak tracking settings, 10 | /// leaks from objects created by this function will be ignored. 11 | void createLeakingWidget() => StatelessLeakingWidget(); 12 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/README.md: -------------------------------------------------------------------------------- 1 | Tests in this folder should be run with flag `--enable-vmservice` 2 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/_dispatcher_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ui'; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:leak_tracker/src/leak_tracking/primitives/_dispatcher.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import '../test_infra/event_tracker.dart'; 12 | 13 | void main() { 14 | test('dispatchObjectEvent dispatches Flutter SDK instrumentation.', () { 15 | final tracker = EventTracker(); 16 | 17 | FlutterMemoryAllocations.instance.addListener( 18 | (event) => dispatchObjectEvent( 19 | event.toMap(), 20 | onStartTracking: tracker.dispatchObjectCreated, 21 | onDispatchDisposal: tracker.dispatchObjectDisposed, 22 | ), 23 | ); 24 | 25 | final picture = _createPicture(); 26 | 27 | expect(tracker.events, hasLength(1)); 28 | var event = tracker.events[0]; 29 | tracker.events.clear(); 30 | expect(event.type, EventType.started); 31 | expect(event.object, picture); 32 | expect(event.context, null); 33 | expect(event.className, 'Picture'); 34 | expect(event.library, 'dart:ui'); 35 | 36 | picture.dispose(); 37 | 38 | expect(tracker.events, hasLength(1)); 39 | event = tracker.events[0]; 40 | tracker.events.clear(); 41 | expect(event.type, EventType.disposed); 42 | expect(event.object, picture); 43 | expect(event.context, null); 44 | }); 45 | } 46 | 47 | Picture _createPicture() { 48 | final recorder = PictureRecorder(); 49 | final canvas = Canvas(recorder); 50 | const rect = Rect.fromLTWH(0.0, 0.0, 100.0, 100.0); 51 | canvas.clipRect(rect); 52 | return recorder.endRecording(); 53 | } 54 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/_formatting_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/src/shared/_formatting.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | const _jsonEmpty = {}; 9 | 10 | const libName = 'libName'; 11 | const _json = { 12 | 'value': { 13 | 'class': { 14 | 'library': {'name': libName}, 15 | }, 16 | }, 17 | }; 18 | 19 | void main() { 20 | test('property returns null for no value', () { 21 | final lib = property(RetainingObjectProperty.lib, _jsonEmpty); 22 | expect(lib, null); 23 | }); 24 | 25 | test('property extracts value', () { 26 | final lib = property(RetainingObjectProperty.lib, _json); 27 | expect(lib, libName); 28 | }); 29 | 30 | test('removeLeakTrackingLines removes lines.', () { 31 | const stackTrace = ''' 32 | #0 ObjectTracker.startTracking (package:leak_tracker/src/leak_tracking/_object_tracker.dart:70:64) 33 | #1 dispatchObjectCreated. (package:leak_tracker/src/leak_tracking/leak_tracker.dart:111:13) 34 | #2 dispatchObjectCreated (package:leak_tracker/src/leak_tracking/leak_tracker.dart:118:4) 35 | #3 new LeakTrackedClass (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker/test/test_infra/data/dart_classes.dart:9:5) 36 | #4 new LeakingClass (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker/test/test_infra/data/dart_classes.dart:31:5) 37 | #5 main.. (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker/test/release/leak_tracking/end_to_end_test.dart:88:9) 38 | #6 withLeakTracking (package:leak_tracker/src/leak_tracking/orchestration.dart:89:19) 39 | #7 main. (file:///Users/polinach/_/leak_tracker/pkgs/leak_tracker/test/release/leak_tracking/end_to_end_test.dart:86:25) 40 | #8 Declarer.test.. (package:test_api/src/backend/declarer.dart:215:19) 41 | 42 | #9 Declarer.test. (package:test_api/src/backend/declarer.dart:213:7) 43 | 44 | #10 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:258:9) 45 | 46 | '''; 47 | final expected = stackTrace.substring(stackTrace.indexOf('#3')); 48 | 49 | final actual = removeLeakTrackingLines(stackTrace); 50 | 51 | expect(actual, expected); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/end_to_end/_retaining_path_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/src/leak_tracking/primitives/_retaining_path/_connection.dart'; 6 | import 'package:leak_tracker/src/leak_tracking/primitives/_retaining_path/_retaining_path.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | // We duplicate testing for retaining path here, 10 | // because there were cases when the tests were passing for dart, 11 | // but not for flutter. 12 | 13 | class MyClass { 14 | MyClass(); 15 | } 16 | 17 | class MyArgClass { 18 | MyArgClass(); 19 | } 20 | 21 | void main() { 22 | test('Path for $MyClass instance is found.', () async { 23 | final instance = MyClass(); 24 | 25 | final connection = await connect(); 26 | 27 | final path = await retainingPath( 28 | connection, 29 | instance, 30 | ); 31 | 32 | expect(path!.elements, isNotEmpty); 33 | }); 34 | 35 | test('Path for class with generic arg is found.', () async { 36 | final instance = MyArgClass(); 37 | final connection = await connect(); 38 | 39 | final path = await retainingPath( 40 | connection, 41 | instance, 42 | ); 43 | expect(path!.elements, isNotEmpty); 44 | }); 45 | 46 | test('Connection can be reused', () async { 47 | final instance1 = MyClass(); 48 | final instance2 = MyClass(); 49 | final connection = await connect(); 50 | 51 | final obtainers = [ 52 | retainingPath(connection, instance1), 53 | retainingPath(connection, instance2), 54 | ]; 55 | 56 | final paths = await Future.wait(obtainers); 57 | 58 | expect(paths, hasLength(2)); 59 | expect(paths.where((p) => p == null).toList(), hasLength(0)); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/end_to_end/leak_detection_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import '../../test_infra/memory_leak_tests.dart'; 9 | 10 | class _TestExecution { 11 | _TestExecution({ 12 | required this.settings, 13 | required this.settingName, 14 | required this.test, 15 | }); 16 | 17 | final String settingName; 18 | final LeakTesting settings; 19 | final LeakTestCase test; 20 | 21 | String get name => '${test.name}, $settingName'; 22 | } 23 | 24 | final List<_TestExecution> _testExecutions = <_TestExecution>[]; 25 | 26 | void main() { 27 | LeakTesting.collectedLeaksReporter = _verifyLeaks; 28 | LeakTesting.enable(); 29 | LeakTesting.settings = LeakTesting.settings.withIgnored( 30 | createdByTestHelpers: true, 31 | testHelperExceptions: [RegExp(RegExp.escape(memoryLeakTestsFilePath()))], 32 | ); 33 | 34 | tearDown(maybeTearDownLeakTrackingForTest); 35 | 36 | for (final t in memoryLeakTests) { 37 | for (final settingsCase in leakTestingSettingsCases.entries) { 38 | final settings = settingsCase.value(LeakTesting.settings); 39 | final execution = _TestExecution( 40 | settingName: settingsCase.key, test: t, settings: settings); 41 | _testExecutions.add(execution); 42 | 43 | test(execution.name, () async { 44 | maybeSetupLeakTrackingForTest(settings, execution.name); 45 | await t.body(null, null); 46 | }); 47 | } 48 | } 49 | } 50 | 51 | void _verifyLeaks(Leaks leaks) { 52 | for (final execution in _testExecutions) { 53 | final testLeaks = leaks.byPhase[execution.name] ?? Leaks.empty(); 54 | execution.test.verifyLeaks(testLeaks, execution.settings, 55 | testDescription: execution.name); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/end_to_end/test_helpers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | /// These tests verify that value of 9 | /// [IgnoredLeaks.createdByTestHelpers] is respected. 10 | void main() { 11 | setUpAll(LeakTesting.enable); 12 | 13 | setUp(() { 14 | LeakTesting.collectedLeaksReporter = (leaks) {}; 15 | maybeSetupLeakTrackingForTest( 16 | LeakTesting.settings.withTrackedAll().withIgnored( 17 | allNotGCed: true, 18 | createdByTestHelpers: true, 19 | ), 20 | '-', 21 | ); 22 | }); 23 | 24 | tearDown(maybeTearDownLeakTrackingForAll); 25 | 26 | test('Prod leak is detected.', () async { 27 | StatelessLeakingWidget(); 28 | 29 | LeakTracking.declareNotDisposedObjectsAsLeaks(); 30 | final leaks = await LeakTracking.collectLeaks(); 31 | expect(leaks.notDisposed.length, 1); 32 | }); 33 | 34 | test('Test leak is ignored.', () async { 35 | _createTestWidget(); 36 | 37 | LeakTracking.declareNotDisposedObjectsAsLeaks(); 38 | final leaks = await LeakTracking.collectLeaks(); 39 | expect(leaks.notDisposed.length, 0); 40 | }); 41 | } 42 | 43 | StatelessLeakingWidget _createTestWidget() => StatelessLeakingWidget(); 44 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/matchers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | class _TrackedClass { 10 | _TrackedClass() { 11 | FlutterMemoryAllocations.instance.dispatchObjectCreated( 12 | library: 'library', 13 | className: '_TrackedClass', 14 | object: this, 15 | ); 16 | } 17 | 18 | void dispose() { 19 | FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); 20 | } 21 | } 22 | 23 | void main() { 24 | test('dispatchesMemoryEvents success sync', () async { 25 | await expectLater( 26 | await memoryEvents(() => _TrackedClass().dispose(), _TrackedClass), 27 | areCreateAndDispose, 28 | ); 29 | }); 30 | 31 | test('dispatchesMemoryEvents failure sync', () async { 32 | try { 33 | await expectLater( 34 | await memoryEvents(() {}, _TrackedClass), 35 | areCreateAndDispose, 36 | ); 37 | } catch (e) { 38 | expect(e, isA()); 39 | } 40 | }); 41 | 42 | test('dispatchesMemoryEvents success async', () async { 43 | await expectLater( 44 | await memoryEvents(() async => _TrackedClass().dispose(), _TrackedClass), 45 | areCreateAndDispose, 46 | ); 47 | }); 48 | 49 | test('dispatchesMemoryEvents failure async', () async { 50 | try { 51 | await expectLater( 52 | await memoryEvents(() async {}, _TrackedClass), 53 | areCreateAndDispose, 54 | ); 55 | } catch (e) { 56 | expect(e, isA()); 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_flutter_testing/test/tests/testing_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | final LeakTesting settings = 9 | LeakTesting.settings.withIgnored(allNotDisposed: true, allNotGCed: true); 10 | 11 | void main() { 12 | group('maybeSetupLeakTrackingForTest', () { 13 | setUp(() { 14 | LeakTesting.enable(); 15 | LeakTesting.settings = LeakTesting.settings.withTrackedAll(); 16 | }); 17 | 18 | tearDown(LeakTracking.stop); 19 | 20 | test('If settings is null, respects globals', () { 21 | maybeSetupLeakTrackingForTest(null, 'myTest1'); 22 | expect(LeakTracking.isStarted, true); 23 | expect(LeakTracking.phase.name, 'myTest1'); 24 | expect(LeakTracking.phase.ignoreLeaks, LeakTesting.settings.ignore); 25 | expect( 26 | LeakTracking.phase.ignoredLeaks, 27 | LeakTesting.settings.ignoredLeaks, 28 | ); 29 | }); 30 | 31 | test('If settings are provided, respects them', () { 32 | maybeSetupLeakTrackingForTest(settings, 'myTest2'); 33 | expect(LeakTracking.isStarted, true); 34 | expect(LeakTracking.phase.name, 'myTest2'); 35 | expect(LeakTracking.phase.ignoreLeaks, settings.ignore); 36 | expect( 37 | LeakTracking.phase.ignoredLeaks, 38 | settings.ignoredLeaks, 39 | ); 40 | }); 41 | }); 42 | 43 | group('maybeTearDownLeakTrackingForTest', () { 44 | setUp(() { 45 | LeakTesting.settings = LeakTesting.settings.withTrackedAll(); 46 | maybeSetupLeakTrackingForTest(null, 'myTest1'); 47 | }); 48 | 49 | tearDown(LeakTracking.stop); 50 | 51 | test('Pauses leak tracking and can be invoked twice', () { 52 | maybeTearDownLeakTrackingForTest(); 53 | expect(LeakTracking.phase.name, null); 54 | expect(LeakTracking.isStarted, true); 55 | expect(LeakTracking.phase.ignoreLeaks, true); 56 | 57 | maybeTearDownLeakTrackingForTest(); 58 | expect(LeakTracking.phase.name, null); 59 | expect(LeakTracking.isStarted, true); 60 | expect(LeakTracking.phase.ignoreLeaks, true); 61 | }); 62 | }); 63 | 64 | group('maybeTearDownLeakTrackingForAll', () { 65 | setUp(() { 66 | LeakTesting.settings = LeakTesting.settings.withTrackedAll(); 67 | maybeSetupLeakTrackingForTest(null, 'myTest1'); 68 | maybeTearDownLeakTrackingForTest(); 69 | }); 70 | 71 | tearDown(LeakTracking.stop); 72 | 73 | test('Stops leak tracking', () async { 74 | await maybeTearDownLeakTrackingForAll(); 75 | expect(LeakTracking.isStarted, false); 76 | }); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.2 2 | 3 | * Update `leak_tracker` dependency to `>=9.0.0 <12.0.0`. 4 | 5 | ## 3.0.1 6 | 7 | * Fixed typo by renaming `experimantalAllNotGCed` to `experimentalAllNotGCed`. 8 | 9 | ## 3.0.0 10 | 11 | * Rename `IgnoredLeaks.notGCed` to `IgnoredLeaks.experimentalNotGCed` 12 | and make notGCed leaks ignored by leak tracking tests by default. 13 | 14 | ## 2.0.3 15 | 16 | * Require Dart SDK 3.2.0 or later. 17 | 18 | ## 2.0.2 19 | 20 | * Allow to ignore objects created by test helpers. 21 | * Set `ignore = false` by default. 22 | 23 | ## 2.0.1 24 | 25 | * Add LeakTesting.enabled. 26 | 27 | ## 2.0.0 28 | 29 | * Remove fields `failOnLeaksCollected` and `onLeaks` from `LeakTesting`. 30 | 31 | ## 1.0.6 32 | 33 | * Updated to use `package:lints/recommended.yaml` for analysis. 34 | * Move LeakTesting from leak_tracker to this library. 35 | 36 | ## 1.0.5 37 | 38 | * Stop depending on test. 39 | 40 | ## 1.0.4 41 | 42 | * Bump version of SDK to 3.1.2. 43 | 44 | ## 1.0.3 45 | 46 | * Update version of leak_tracker to `^9.0.0`. 47 | 48 | ## 1.0.2 49 | 50 | * Update version of leak_tracker to `^8.0.0`. 51 | * Set version of leak_tracker to `any`. 52 | 53 | ## 1.0.0 54 | 55 | * Create version. 56 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/leak_tracker_testing.svg)](https://pub.dev/packages/leak_tracker_testing) 2 | [![package publisher](https://img.shields.io/pub/publisher/leak_tracker_testing.svg)](https://pub.dev/packages/leak_tracker_testing/publisher) 3 | 4 | ## What is this? 5 | 6 | This package contains the test-only leak tracking APIs for pure dart projects. 7 | 8 | They are separated from `leak_tracker` to make sure 9 | testing code is not used in production. 10 | 11 | Read more in [leak tracking overview](https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/OVERVIEW.md). 12 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | 8 | linter: 9 | rules: 10 | - avoid_print 11 | - sort_constructors_first 12 | - sort_unnamed_constructors_first 13 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/lib/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | leak_tracker_testing.dart-->src; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/lib/leak_tracker_testing.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/leak_testing.dart'; 6 | export 'src/matchers.dart'; 7 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/lib/src/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | leak_testing.dart-->matchers.dart; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/lib/src/matchers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | import 'package:matcher/matcher.dart'; 7 | 8 | /// Checks if the leak collection is empty. 9 | const Matcher isLeakFree = _IsLeakFree(); 10 | 11 | class _IsLeakFree extends Matcher { 12 | const _IsLeakFree(); 13 | 14 | @override 15 | bool matches(Object? item, Map matchState) => 16 | item is Leaks && item.total == 0; 17 | 18 | @override 19 | Description describeMismatch( 20 | Object? item, 21 | Description mismatchDescription, 22 | Map matchState, 23 | bool verbose, 24 | ) { 25 | if (item is! Leaks) { 26 | return mismatchDescription 27 | ..add( 28 | 'The matcher applies to $Leaks and cannot be ' 29 | 'applied to ${item.runtimeType}', 30 | ); 31 | } 32 | 33 | return mismatchDescription 34 | ..add('contains leaks:\n${item.toYaml(phasesAreTests: true)}'); 35 | } 36 | 37 | @override 38 | Description describe(Description description) => description.add('leak free'); 39 | } 40 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leak_tracker_testing 2 | version: 3.0.2 3 | description: Leak tracking code intended for usage in tests. 4 | repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_testing 5 | 6 | environment: 7 | sdk: ^3.2.0 8 | 9 | # All dependencies here will become Flutter transitive dependencies. 10 | # Follow the process when adding: 11 | # https://github.com/flutter/flutter/blob/2be9b613099250cf8e84e1498a7624d9d2e04023/dev/bots/allowlist.dart#L13 12 | dependencies: 13 | leak_tracker: '>=9.0.0 <12.0.0' 14 | matcher: ^0.12.16 15 | meta: ^1.11.0 16 | 17 | dev_dependencies: 18 | dart_flutter_team_lints: ^3.1.0 19 | layerlens: ^1.0.16 20 | test: ^1.16.0 21 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | leak_tracker: 3 | path: ../leak_tracker 4 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/test/leak_testing_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/src/leak_tracking/primitives/model.dart'; 6 | import 'package:leak_tracker_testing/src/leak_testing.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | group('$LeakTesting', () { 11 | test('debug info preserves other settings', () { 12 | final settings = LeakTesting.settings 13 | .withIgnored(notDisposed: {'MyClass': 1}) 14 | .withIgnored(createdByTestHelpers: true) 15 | .withIgnored() 16 | .withCreationStackTrace() 17 | .withDisposalStackTrace() 18 | .withRetainingPath(); 19 | 20 | expect( 21 | settings.leakDiagnosticConfig.collectRetainingPathForNotGCed, 22 | true, 23 | ); 24 | expect( 25 | settings.leakDiagnosticConfig.collectStackTraceOnDisposal, 26 | true, 27 | ); 28 | expect( 29 | settings.leakDiagnosticConfig.collectStackTraceOnStart, 30 | true, 31 | ); 32 | expect( 33 | settings.ignoredLeaks.notDisposed.byClass.keys.firstOrNull, 34 | 'MyClass', 35 | ); 36 | expect( 37 | settings.ignoredLeaks.createdByTestHelpers, 38 | true, 39 | ); 40 | }); 41 | 42 | group('withTracked', () { 43 | test('not provided args do not affect the instance, tracked', () { 44 | final settings = LeakTesting.settings.withTrackedAll().withIgnored( 45 | allNotDisposed: true, 46 | allNotGCed: true, 47 | createdByTestHelpers: true, 48 | testHelperExceptions: [RegExp('my_test.dart')], 49 | ); 50 | 51 | expect(settings.ignoredLeaks.notDisposed.ignoreAll, true); 52 | expect(settings.ignoredLeaks.experimentalNotGCed.ignoreAll, true); 53 | 54 | final tracked = settings.withTracked( 55 | allNotDisposed: true, experimentalAllNotGCed: true); 56 | 57 | expect(tracked.ignoredLeaks.notDisposed.ignoreAll, false); 58 | expect(tracked.ignoredLeaks.experimentalNotGCed.ignoreAll, false); 59 | expect(tracked.ignoredLeaks.createdByTestHelpers, true); 60 | expect(tracked.ignoredLeaks.testHelperExceptions, hasLength(1)); 61 | }); 62 | }); 63 | 64 | group('withIgnored and withTracked', () { 65 | test('not provided args do not affect the instance, tracked', () { 66 | final settings = LeakTesting.settings 67 | .withTrackedAll() 68 | .withTracked(experimentalAllNotGCed: true); 69 | 70 | expect(settings.ignore, false); 71 | expect(settings.ignoredLeaks.notDisposed.ignoreAll, false); 72 | expect(settings.ignoredLeaks.notDisposed.byClass, {}); 73 | expect(settings.ignoredLeaks.experimentalNotGCed.ignoreAll, false); 74 | expect(settings.ignoredLeaks.experimentalNotGCed.byClass, 75 | {}); 76 | 77 | expect(settings.withIgnored(), settings); 78 | expect(settings.withTracked(), settings); 79 | 80 | final withPath = settings 81 | .withRetainingPath() 82 | .copyWith(leakDiagnosticConfig: const LeakDiagnosticConfig()); 83 | expect(withPath, settings); 84 | }); 85 | 86 | test('not provided args do not affect the instance, ignored', () { 87 | final settings = LeakTesting.settings.withIgnoredAll().withIgnored( 88 | allNotDisposed: true, 89 | allNotGCed: true, 90 | classes: ['MyClass'], 91 | testHelperExceptions: [RegExp('my_test.dart')], 92 | ); 93 | 94 | expect(settings.ignore, true); 95 | expect(settings.ignoredLeaks.notDisposed.ignoreAll, true); 96 | expect(settings.ignoredLeaks.notDisposed.byClass, hasLength(1)); 97 | expect(settings.ignoredLeaks.experimentalNotGCed.ignoreAll, true); 98 | expect(settings.ignoredLeaks.experimentalNotGCed.byClass, hasLength(1)); 99 | 100 | expect(settings.withIgnored(), settings); 101 | expect(settings.withTracked(), settings); 102 | 103 | final withPath = settings 104 | .withRetainingPath() 105 | .copyWith(leakDiagnosticConfig: const LeakDiagnosticConfig()); 106 | expect(withPath, settings); 107 | }); 108 | }); 109 | 110 | group('equals', () { 111 | test('trivial', () { 112 | final settings1 = LeakTesting.settings.copyWith(); 113 | final settings2 = LeakTesting.settings.copyWith(); 114 | 115 | expect(settings1 == settings2, true); 116 | }); 117 | 118 | test('customized equal', () { 119 | final settings1 = LeakTesting.settings.copyWith( 120 | leakDiagnosticConfig: const LeakDiagnosticConfig(), 121 | ignoredLeaks: const IgnoredLeaks(), 122 | ); 123 | final settings2 = LeakTesting.settings.copyWith( 124 | leakDiagnosticConfig: const LeakDiagnosticConfig(), 125 | ignoredLeaks: const IgnoredLeaks(), 126 | ); 127 | 128 | expect(settings1 == settings2, true); 129 | }); 130 | 131 | test('different', () { 132 | const list1 = IgnoredLeaks( 133 | notDisposed: IgnoredLeaksSet(byClass: {'MyClass': null}), 134 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 135 | ); 136 | const list2 = IgnoredLeaks( 137 | notDisposed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 138 | experimentalNotGCed: IgnoredLeaksSet(byClass: {'MyClass': 1}), 139 | ); 140 | expect(list1 == list2, false); 141 | }); 142 | }); 143 | }); 144 | } 145 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_testing/test/matchers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:leak_tracker/leak_tracker.dart'; 6 | import 'package:leak_tracker_testing/leak_tracker_testing.dart'; 7 | 8 | import 'package:test/test.dart'; 9 | 10 | final _leaks = Leaks({ 11 | LeakType.gcedLate: [ 12 | LeakReport( 13 | trackedClass: 'trackedClass', 14 | context: {}, 15 | code: 1, 16 | type: 'type', 17 | phase: null, 18 | ), 19 | ], 20 | }); 21 | 22 | void main() { 23 | test('$isLeakFree passes.', () async { 24 | expect(Leaks({}), isLeakFree); 25 | }); 26 | 27 | test('$isLeakFree fails.', () async { 28 | expect(isLeakFree.matches(_leaks, {}), false); 29 | }); 30 | 31 | test('$isLeakFree fails.', () async { 32 | expect(isLeakFree.matches(_leaks, {}), false); 33 | }); 34 | 35 | group('troubleshootingDocumentationLink', () { 36 | late String originalLink; 37 | setUp(() { 38 | originalLink = LeakTracking.troubleshootingDocumentationLink; 39 | }); 40 | tearDown(() { 41 | LeakTracking.troubleshootingDocumentationLink = originalLink; 42 | }); 43 | 44 | test('defaults to TROUBLESHOOT.md', () async { 45 | expect(LeakTracking.troubleshootingDocumentationLink, 46 | 'https://github.com/dart-lang/leak_tracker/blob/main/doc/leak_tracking/TROUBLESHOOT.md'); 47 | }); 48 | 49 | test('is preserved', () async { 50 | expect(LeakTracking.troubleshootingDocumentationLink, originalLink); 51 | LeakTracking.troubleshootingDocumentationLink = 'https://example.com'; 52 | expect( 53 | LeakTracking.troubleshootingDocumentationLink, 'https://example.com'); 54 | }); 55 | 56 | test('is preserved', () async { 57 | expect(LeakTracking.troubleshootingDocumentationLink, originalLink); 58 | LeakTracking.troubleshootingDocumentationLink = 'https://example.com'; 59 | expect( 60 | LeakTracking.troubleshootingDocumentationLink, 'https://example.com'); 61 | }); 62 | 63 | test('is used in leak report when default', () async { 64 | expect(LeakTracking.troubleshootingDocumentationLink, originalLink); 65 | checkLinkIsUsed(); 66 | }); 67 | 68 | test('is used in leak report when default', () async { 69 | LeakTracking.troubleshootingDocumentationLink = 70 | 'https://custom_example.com'; 71 | expect(LeakTracking.troubleshootingDocumentationLink, 72 | 'https://custom_example.com'); 73 | checkLinkIsUsed(); 74 | }); 75 | }); 76 | } 77 | 78 | void checkLinkIsUsed() { 79 | final description = isLeakFree.describeMismatch( 80 | _leaks, StringDescription(), {}, false) as StringDescription; 81 | expect(description.toString(), 82 | contains(LeakTracking.troubleshootingDocumentationLink)); 83 | } 84 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/README.md: -------------------------------------------------------------------------------- 1 | # Web tests for leak tracker 2 | 3 | This package contains web-platform tests for leak tracker. 4 | 5 | The tests are in separate package, because: 6 | 1. `flutter test --platform chrome` fails for any test folder except `test`. 7 | 2. 'normal' testing for leak tracker uses vm service, that is not available for web. 8 | 3. Web testing is not possible with `package:test`, we need `package:flutter_test`, 9 | and there are complications because it depends on concrete version of leak_tracker_flutter_testing. 10 | 11 | Command to run tests: 12 | 13 | ``` 14 | flutter test --platform chrome 15 | ``` 16 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | 8 | linter: 9 | rules: 10 | - avoid_print 11 | - sort_constructors_first 12 | - sort_unnamed_constructors_first 13 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: web_tests 2 | publish_to: 'none' 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ^3.2.0 8 | flutter: '>=3.18.0-18.0.pre.54' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | dart_flutter_team_lints: ^3.1.0 16 | 17 | flutter_test: 18 | sdk: flutter 19 | layerlens: ^1.0.16 20 | leak_tracker_flutter_testing: any # version is defined by flutter 21 | 22 | flutter: 23 | uses-material-design: true 24 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | leak_tracker: 3 | path: ../leak_tracker 4 | leak_tracker_testing: 5 | path: ../leak_tracker_testing 6 | leak_tracker_flutter_testing: 7 | path: ../leak_tracker_flutter_testing 8 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/test/leak_detection_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 8 | 9 | class _TestExecution { 10 | _TestExecution({ 11 | required this.settings, 12 | required this.settingName, 13 | required this.test, 14 | }); 15 | 16 | final String settingName; 17 | final LeakTesting settings; 18 | final LeakTestCase test; 19 | 20 | String get name => '${test.name}, $settingName'; 21 | } 22 | 23 | final List<_TestExecution> _testExecutions = <_TestExecution>[]; 24 | 25 | void main() { 26 | LeakTesting.collectedLeaksReporter = _verifyLeaks; 27 | LeakTesting.enable(); 28 | LeakTesting.settings = LeakTesting.settings 29 | .withTrackedAll() 30 | .withTracked(allNotDisposed: true, experimentalAllNotGCed: false); 31 | 32 | tearDown(maybeTearDownLeakTrackingForTest); 33 | 34 | for (final t in _memoryLeakTests) { 35 | final execution = _TestExecution( 36 | settingName: 'not disposed', 37 | test: t, 38 | settings: LeakTesting.settings, 39 | ); 40 | _testExecutions.add(execution); 41 | 42 | testWidgets(execution.name, (tester) async { 43 | maybeSetupLeakTrackingForTest(LeakTesting.settings, execution.name); 44 | await t.body( 45 | (Widget widget, [Duration? duration]) => 46 | tester.pumpWidget(widget, duration: duration), 47 | (callback) => tester.runAsync(callback), 48 | ); 49 | }); 50 | } 51 | } 52 | 53 | void _verifyLeaks(Leaks leaks) { 54 | for (final execution in _testExecutions) { 55 | final testLeaks = leaks.byPhase[execution.name] ?? Leaks.empty(); 56 | execution.test.verifyLeaks( 57 | testLeaks, 58 | execution.settings, 59 | testDescription: execution.name, 60 | ); 61 | } 62 | } 63 | 64 | /// Test cases for memory leaks. 65 | final List _memoryLeakTests = [ 66 | LeakTestCase( 67 | name: 'no leaks', 68 | body: ( 69 | PumpWidgetsCallback? pumpWidgets, 70 | RunAsyncCallback? runAsync, 71 | ) async { 72 | Container(); 73 | }, 74 | ), 75 | LeakTestCase( 76 | name: 'not disposed disposable', 77 | body: ( 78 | PumpWidgetsCallback? pumpWidgets, 79 | RunAsyncCallback? runAsync, 80 | ) async { 81 | InstrumentedDisposable(); 82 | }, 83 | notDisposedTotal: 1, 84 | ), 85 | LeakTestCase( 86 | name: 'leaking widget', 87 | body: ( 88 | PumpWidgetsCallback? pumpWidgets, 89 | RunAsyncCallback? runAsync, 90 | ) async { 91 | StatelessLeakingWidget(); 92 | }, 93 | notDisposedTotal: 1, 94 | ), 95 | ]; 96 | -------------------------------------------------------------------------------- /pkgs/leak_tracker_web_tests/test/smoke_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | void main() { 9 | setUp(() {}); 10 | 11 | // Tests that widget testing for web is in general working. 12 | testWidgets('Smoke test', (WidgetTester tester) async { 13 | await tester.pumpWidget(Container()); 14 | await tester.runAsync(() async { 15 | await tester.pump(); 16 | }); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/memory_usage/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 2 | 3 | * First release. 4 | -------------------------------------------------------------------------------- /pkgs/memory_usage/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/memory_usage/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/memory_usage.svg)](https://pub.dev/packages/memory_usage) 2 | [![package publisher](https://img.shields.io/pub/publisher/memory_usage.svg)](https://pub.dev/packages/memory_usage/publisher) 3 | 4 | # memory_usage 5 | 6 | ## Status: Experimental 7 | 8 | **NOTE**: This package is currently experimental and published under the 9 | [labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order to 10 | solicit feedback. 11 | 12 | For packages in the labs.dart.dev publisher we generally plan to either graduate 13 | the package into a supported publisher (dart.dev, tools.dart.dev) after a period 14 | of feedback and iteration, or discontinue the package. These packages have a 15 | much higher expected rate of API and breaking changes. 16 | 17 | Your feedback is valuable and will help us evolve this package. For general 18 | feedback, suggestions, and comments, please file an issue in the 19 | [bug tracker](https://github.com/dart-lang/leak_tracker/issues). 20 | 21 | ## What is this? 22 | 23 | This package enables [memory usage tracking and auto-snapshotting](https://github.com/dart-lang/leak_tracker/blob/main/doc/USAGE.md). 24 | 25 | ## Usage 26 | 27 | Use the function `trackMemoryUsage` to configure usage events and auto-snapshotting in your Dart or Flutter application. 28 | 29 | See [usage tracking guidance](https://github.com/dart-lang/leak_tracker/blob/main/doc/USAGE.md) for more details. 30 | -------------------------------------------------------------------------------- /pkgs/memory_usage/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | - sort_constructors_first 6 | - sort_unnamed_constructors_first 7 | -------------------------------------------------------------------------------- /pkgs/memory_usage/example/README.md: -------------------------------------------------------------------------------- 1 | Minimal Dart application with memory usage tracking enabled. 2 | -------------------------------------------------------------------------------- /pkgs/memory_usage/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | -------------------------------------------------------------------------------- /pkgs/memory_usage/example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:memory_usage/memory_usage.dart'; 6 | 7 | void main(List arguments) { 8 | final config = UsageTrackingConfig( 9 | autoSnapshottingConfig: AutoSnapshottingConfig( 10 | onSnapshot: (SnapshotEvent event) {}, 11 | thresholdMb: 400, 12 | increaseMb: 100, 13 | directorySizeLimitMb: 500, 14 | directory: 'snapshots', 15 | minDelayBetweenSnapshots: const Duration(seconds: 5), 16 | ), 17 | usageEventsConfig: UsageEventsConfig( 18 | (MemoryUsageEvent event) {}, 19 | deltaMb: 100, 20 | ), 21 | ); 22 | 23 | trackMemoryUsage(config); 24 | } 25 | -------------------------------------------------------------------------------- /pkgs/memory_usage/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: memory_usage_example 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.2.0 6 | 7 | dependencies: 8 | memory_usage: ^0.1.0 9 | 10 | dev_dependencies: 11 | lints: ^4.0.0 12 | 13 | dependency_overrides: 14 | memory_usage: 15 | path: .. 16 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | memory_usage.dart-->src; 9 | ``` 10 | 11 | ### Inversions 12 | In this folder: 0 13 | 14 | Including sub-folders: 0 15 | 16 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/memory_usage.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/auto_snapshotting/auto_snapshotting.dart'; 6 | export 'src/auto_snapshotting/model.dart'; 7 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ```mermaid 7 | flowchart TD; 8 | _snapshot.dart-->_util.dart; 9 | _snapshot.dart-->model.dart; 10 | _usage_event.dart-->_util.dart; 11 | _usage_event.dart-->model.dart; 12 | auto_snapshotting.dart-->_snapshot.dart; 13 | auto_snapshotting.dart-->_usage_event.dart; 14 | auto_snapshotting.dart-->model.dart; 15 | ``` 16 | 17 | ### Inversions 18 | In this folder: 0 19 | 20 | Including sub-folders: 0 21 | 22 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/_snapshot.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:developer'; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as path; 9 | 10 | import '_util.dart'; 11 | import 'model.dart'; 12 | 13 | class AutoSnapshotter { 14 | AutoSnapshotter(this.config); 15 | 16 | final AutoSnapshottingConfig config; 17 | 18 | bool _snapshottingIsInProgress = false; 19 | SnapshotEvent? _previousSnapshot; 20 | bool _noSnapshotsAnyMore = false; 21 | 22 | Future autoSnapshot() async { 23 | if (_noSnapshotsAnyMore || _snapshottingIsInProgress) return; 24 | _snapshottingIsInProgress = true; 25 | await _maybeTakeSnapshot(); 26 | _snapshottingIsInProgress = false; 27 | } 28 | 29 | Future _maybeTakeSnapshot() async { 30 | final rss = ProcessInfo.currentRss; 31 | if (rss < config.thresholdMb.mbToBytes) { 32 | return; 33 | } 34 | 35 | // Directory size validation is heavier than rss check, so we do it after. 36 | // We do not stop monitoring, in case user will free some space. 37 | if (_isDirectoryOversized()) return; 38 | 39 | final stepMb = config.increaseMb; 40 | 41 | if (_previousSnapshot == null) { 42 | _takeSnapshot(rss: rss); 43 | if (stepMb == null) _noSnapshotsAnyMore = true; 44 | return; 45 | } 46 | 47 | final previous = _previousSnapshot!; 48 | 49 | if (stepMb == null) { 50 | throw StateError( 51 | 'Autosnapshotting should be off if step is null and there is a ' 52 | 'snapshot already taken', 53 | ); 54 | } 55 | 56 | final nextAllowedSnapshotTime = 57 | previous.timestamp.add(config.minDelayBetweenSnapshots); 58 | if (nextAllowedSnapshotTime.isAfter(DateTime.now())) { 59 | return; 60 | } 61 | 62 | final nextThreshold = previous.rss + stepMb.mbToBytes; 63 | if (rss >= nextThreshold) { 64 | _takeSnapshot(rss: rss); 65 | } 66 | } 67 | 68 | void _takeSnapshot({required int rss}) { 69 | final snapshotNumber = (_previousSnapshot?.snapshotNumber ?? 0) + 1; 70 | 71 | final snapshot = saveSnapshot( 72 | config.directory, 73 | rss: rss, 74 | snapshotNumber: snapshotNumber, 75 | ); 76 | _previousSnapshot = snapshot; 77 | config.onSnapshot?.call(snapshot); 78 | } 79 | 80 | bool _isDirectoryOversized() { 81 | final directorySize = Directory(config.directory) 82 | .listSync(recursive: true) 83 | .whereType() 84 | .map((f) => f.lengthSync()) 85 | .fold(0, (a, b) => a + b); 86 | return directorySize >= config.directorySizeLimitMb.mbToBytes; 87 | } 88 | } 89 | 90 | /// Saves a memory heap snapshot of the current process, to the [directory]. 91 | /// 92 | /// Returns the name of the file where the snapshot was saved. 93 | /// 94 | /// The file name contains process id, snapshot number and current RSS. 95 | SnapshotEvent saveSnapshot( 96 | String directory, { 97 | required int rss, 98 | required int snapshotNumber, 99 | void Function(String fileName) snapshotter = 100 | NativeRuntime.writeHeapSnapshotToFile, 101 | }) { 102 | final fileName = path.absolute( 103 | path.join(directory, 'snapshot-$pid-$snapshotNumber-rss$rss.json'), 104 | ); 105 | 106 | snapshotter(fileName); 107 | 108 | return SnapshotEvent(fileName, snapshotNumber: snapshotNumber, rss: rss); 109 | } 110 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/_usage_event.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import '_util.dart'; 8 | import 'model.dart'; 9 | 10 | class UsageEventCreator { 11 | UsageEventCreator(this.config); 12 | 13 | final UsageEventsConfig config; 14 | 15 | late MemoryUsageEvent _previousEvent; 16 | 17 | void createFirstUsageEvent() => _triggerEvent( 18 | MemoryUsageEvent( 19 | delta: null, 20 | previousEventTime: null, 21 | rss: ProcessInfo.currentRss, 22 | ), 23 | ); 24 | 25 | void mayBeCreateUsageEvent() { 26 | final rss = ProcessInfo.currentRss; 27 | final delta = (rss - _previousEvent.rss).abs(); 28 | 29 | if (delta < config.deltaMb.mbToBytes) return; 30 | 31 | _triggerEvent( 32 | MemoryUsageEvent( 33 | delta: delta, 34 | previousEventTime: _previousEvent.timestamp, 35 | rss: ProcessInfo.currentRss, 36 | ), 37 | ); 38 | } 39 | 40 | void _triggerEvent(MemoryUsageEvent event) { 41 | _previousEvent = event; 42 | config.onUsageEvent(event); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/_util.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | extension SizeConversion on int { 6 | int get mbToBytes => this * 1024 * 1024; 7 | } 8 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/auto_snapshotting.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import '_snapshot.dart'; 9 | import '_usage_event.dart'; 10 | import 'model.dart'; 11 | 12 | Timer? timer; 13 | AutoSnapshotter? autoSnapshotter; 14 | UsageEventCreator? usageEventCreator; 15 | 16 | /// Enables memory usage tracking, based on the value of 17 | /// [ProcessInfo.currentRss] (dart:io). 18 | /// 19 | /// If tracking is already enabled, resets it. 20 | /// See [UsageTrackingConfig] for details. 21 | /// Use [stopMemoryUsageTracking] to stop auto-snapshotting. 22 | /// Snapshotting operation may cause a delay in the main thread. 23 | void trackMemoryUsage(UsageTrackingConfig config) { 24 | stopMemoryUsageTracking(); 25 | if (config.isNoOp) return; 26 | 27 | final autosnapshotting = config.autoSnapshottingConfig; 28 | if (autosnapshotting != null) { 29 | final dir = Directory(autosnapshotting.directory); 30 | if (!dir.existsSync()) { 31 | dir.createSync(recursive: true); 32 | } 33 | autoSnapshotter = AutoSnapshotter(autosnapshotting); 34 | } 35 | 36 | if (config.usageEventsConfig != null) { 37 | usageEventCreator = UsageEventCreator(config.usageEventsConfig!); 38 | usageEventCreator!.createFirstUsageEvent(); 39 | } 40 | 41 | timer = Timer.periodic(config.interval, (_) async { 42 | usageEventCreator?.mayBeCreateUsageEvent(); 43 | await autoSnapshotter?.autoSnapshot(); 44 | }); 45 | } 46 | 47 | /// Stops memory usage tracking if it is started by [trackMemoryUsage]. 48 | void stopMemoryUsageTracking() { 49 | timer?.cancel(); 50 | timer = null; 51 | 52 | autoSnapshotter = null; 53 | usageEventCreator = null; 54 | } 55 | -------------------------------------------------------------------------------- /pkgs/memory_usage/lib/src/auto_snapshotting/model.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:path/path.dart' as path; 6 | 7 | /// A record of a taken snapshot. 8 | class SnapshotEvent { 9 | SnapshotEvent( 10 | this.fileName, { 11 | required this.snapshotNumber, 12 | required this.rss, 13 | DateTime? timestamp, 14 | }) : timestamp = timestamp ?? DateTime.now(); 15 | 16 | final String fileName; 17 | final int snapshotNumber; 18 | final int rss; 19 | final DateTime timestamp; 20 | } 21 | 22 | /// A callback that is called when a snapshot is taken. 23 | typedef SnapshotCallback = void Function(SnapshotEvent event); 24 | 25 | /// A record of a memory usage event. 26 | class MemoryUsageEvent { 27 | MemoryUsageEvent({ 28 | required this.delta, 29 | required DateTime? previousEventTime, 30 | required this.rss, 31 | DateTime? timestamp, 32 | }) : timestamp = timestamp ?? DateTime.now() { 33 | if (previousEventTime == null) { 34 | period = null; 35 | } else { 36 | period = this.timestamp.difference(previousEventTime); 37 | } 38 | } 39 | 40 | /// Difference with previous rss value. 41 | /// 42 | /// Equals to `null` for first event. 43 | final int? delta; 44 | 45 | /// Time since previous event. 46 | /// 47 | /// Equals to `null` for first event. 48 | late Duration? period; 49 | 50 | /// RSS memory usage. 51 | final int rss; 52 | 53 | /// 54 | final DateTime timestamp; 55 | } 56 | 57 | /// A callback that is called for memory usage event. 58 | typedef UsageCallback = void Function(MemoryUsageEvent event); 59 | 60 | /// Configures memory usage tracking. 61 | /// 62 | /// Set [interval] to customize how often to verify memory usage. 63 | class UsageTrackingConfig { 64 | const UsageTrackingConfig({ 65 | this.usageEventsConfig, 66 | this.autoSnapshottingConfig, 67 | this.interval = const Duration(seconds: 1), 68 | }); 69 | 70 | /// Configuration for snapshotting. 71 | final AutoSnapshottingConfig? autoSnapshottingConfig; 72 | 73 | /// Configuration for usage events. 74 | final UsageEventsConfig? usageEventsConfig; 75 | 76 | /// How often to verify memory usage. 77 | final Duration interval; 78 | 79 | bool get isNoOp => 80 | autoSnapshottingConfig == null && usageEventsConfig == null; 81 | 82 | @override 83 | String toString() { 84 | return 'interval: $interval\n' 85 | '$usageEventsConfig\n' 86 | '$autoSnapshottingConfig'; 87 | } 88 | } 89 | 90 | /// Configures memory usage tracking. 91 | /// 92 | /// [onUsageEvent] will be triggered when rss value changes 93 | /// more then by [deltaMb] since previous [onUsageEvent]. 94 | /// First [onUsageEvent] will be triggered immediately. 95 | class UsageEventsConfig { 96 | const UsageEventsConfig( 97 | this.onUsageEvent, { 98 | this.deltaMb = 128, 99 | }); 100 | 101 | /// A callback that is called when a snapshot is taken. 102 | final UsageCallback onUsageEvent; 103 | 104 | /// Change in memory usage to trigger [onUsageEvent]. 105 | final int deltaMb; 106 | } 107 | 108 | /// Configures auto-snapshotting, based on the value of `ProcessInfo.currentRss` 109 | /// (dart:io). 110 | /// 111 | /// Automatic snapshots will begin to be taken when the rss value exceeds 112 | /// [thresholdMb]. The snapshots will be saved to [directory]. 113 | /// 114 | /// The snapshots will be re-taken when the value 115 | /// increases more than by [increaseMb] since previous snapshot, 116 | /// until the size of [directory] exceeds 117 | /// [directorySizeLimitMb]. 118 | /// 119 | /// The [onSnapshot] callback is called when a snapshot is taken. 120 | /// 121 | /// Set [minDelayBetweenSnapshots] to make sure snapshots do not trigger each 122 | /// other. 123 | class AutoSnapshottingConfig { 124 | AutoSnapshottingConfig({ 125 | this.thresholdMb = 1024, // 1Gb 126 | this.increaseMb = 512, // 0.5Gb 127 | this.directory = 'dart_memory_snapshots', 128 | this.directorySizeLimitMb = 10240, // 10Gb 129 | this.minDelayBetweenSnapshots = const Duration(seconds: 10), 130 | this.onSnapshot, 131 | }) { 132 | if (minDelayBetweenSnapshots <= Duration.zero) { 133 | throw ArgumentError.value( 134 | minDelayBetweenSnapshots, 135 | 'minDelayBetweenSnapshots', 136 | 'must be positive', 137 | ); 138 | } 139 | } 140 | 141 | /// The rss value in Mb that will trigger the first snapshot. 142 | final int thresholdMb; 143 | 144 | /// The value by which the rss value should increase, since 145 | /// previous snapshot, to take another snapshot. 146 | /// 147 | /// If [increaseMb] is null, only one snapshot will be taken. 148 | final int? increaseMb; 149 | 150 | /// The directory where snapshots will be saved. 151 | /// 152 | /// If the directory does not exist, it will be created. 153 | /// 154 | /// If the path is relative, it will be relative to the current working 155 | /// directory. 156 | final String directory; 157 | 158 | /// The size limit for the [directory] in Mb. 159 | /// 160 | /// The directory size will be checked before a snapshot is taken and saved, 161 | /// so the directory may exceed the size specified by [directorySizeLimitMb] 162 | /// depending on the size of the snapshot. 163 | final int directorySizeLimitMb; 164 | 165 | /// How long to wait after taking a snapshot before taking another one. 166 | final Duration minDelayBetweenSnapshots; 167 | 168 | /// A callback that is called when a snapshot is taken. 169 | final SnapshotCallback? onSnapshot; 170 | 171 | /// The absolute path to the [directory]. 172 | String get directoryAbsolute => path.absolute(directory); 173 | } 174 | -------------------------------------------------------------------------------- /pkgs/memory_usage/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: memory_usage 2 | version: 0.1.0 3 | description: A framework for memory usage tracking and snapshotting. 4 | repository: https://github.com/dart-lang/leak_tracker/tree/main/pkgs/memory_usage 5 | 6 | environment: 7 | sdk: ^3.2.0 8 | 9 | dependencies: 10 | path: ^1.8.3 11 | 12 | dev_dependencies: 13 | dart_flutter_team_lints: ^3.1.0 14 | layerlens: ^1.0.16 15 | test: ^1.16.0 16 | -------------------------------------------------------------------------------- /pkgs/memory_usage/test/_snapshot_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:memory_usage/src/auto_snapshotting/_snapshot.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | test('saveSnapshot invokes snapshotter', () { 12 | late String actualFileName; 13 | 14 | final returnedFileName = saveSnapshot( 15 | 'directory', 16 | rss: ProcessInfo.currentRss, 17 | snapshotNumber: 1, 18 | snapshotter: (fileName) => actualFileName = fileName, 19 | ).fileName; 20 | 21 | expect(returnedFileName, actualFileName); 22 | expect(returnedFileName, contains('/snapshot-')); 23 | expect(returnedFileName, endsWith('.json')); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /pkgs/memory_usage/test/model_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:memory_usage/memory_usage.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | test('$MemoryUsageEvent initial', () { 10 | final event = 11 | MemoryUsageEvent(rss: 100, delta: null, previousEventTime: null); 12 | 13 | expect(event.delta, null); 14 | expect(event.period, null); 15 | expect(event.rss, 100); 16 | }); 17 | 18 | test('$MemoryUsageEvent delta', () { 19 | final event = MemoryUsageEvent( 20 | rss: 200, 21 | delta: 100, 22 | previousEventTime: DateTime(2022), 23 | timestamp: DateTime(2023), 24 | ); 25 | 26 | expect(event.delta, 100); 27 | expect(event.period, const Duration(days: 365)); 28 | expect(event.rss, 200); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /tool/analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2023 The Chromium Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style license that can be 5 | # found in the LICENSE file. 6 | 7 | # Runs `analyze` for all code in the repo. 8 | 9 | # Fast fail the script on failures. 10 | set -ex 11 | 12 | # The directory that this script is located in. 13 | TOOL_DIR=`dirname "$0"` 14 | 15 | sh $TOOL_DIR/pub_get.sh 16 | 17 | cd $TOOL_DIR/../examples/autosnapshotting 18 | flutter analyze --fatal-infos 19 | cd - 20 | 21 | cd $TOOL_DIR/../examples/leak_tracking 22 | flutter analyze --fatal-infos 23 | cd - 24 | 25 | cd $TOOL_DIR/../pkgs/leak_tracker 26 | dart analyze --fatal-infos 27 | cd - 28 | 29 | cd $TOOL_DIR/../pkgs/leak_tracker_flutter_testing 30 | flutter analyze --fatal-infos 31 | cd - 32 | 33 | cd $TOOL_DIR/../pkgs/leak_tracker_testing 34 | dart analyze --fatal-infos 35 | cd - 36 | 37 | cd $TOOL_DIR/../pkgs/leak_tracker_web_tests 38 | dart analyze --fatal-infos 39 | cd - 40 | 41 | cd $TOOL_DIR/../pkgs/memory_usage 42 | dart analyze --fatal-infos 43 | cd - 44 | -------------------------------------------------------------------------------- /tool/diagrams.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2023 The Chromium Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style license that can be 5 | # found in the LICENSE file. 6 | 7 | # Regenerates dependency diagrams for all packages in the repo. 8 | 9 | # Fast fail the script on failures. 10 | set -ex 11 | 12 | # The directory that this script is located in. 13 | TOOL_DIR=`dirname "$0"` 14 | 15 | sh $TOOL_DIR/pub_get.sh 16 | 17 | cd $TOOL_DIR/../pkgs/leak_tracker 18 | dart run layerlens 19 | cd - 20 | 21 | cd $TOOL_DIR/../pkgs/leak_tracker_testing 22 | dart run layerlens 23 | cd - 24 | 25 | cd $TOOL_DIR/../pkgs/leak_tracker_flutter_testing 26 | dart run layerlens 27 | cd - 28 | 29 | cd $TOOL_DIR/../pkgs/memory_usage 30 | dart run layerlens 31 | cd - 32 | -------------------------------------------------------------------------------- /tool/lt_publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2023 The Chromium Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style license that can be 5 | # found in the LICENSE file. 6 | 7 | # Runs `publish` for leak tracking packages in the repo in right order. 8 | 9 | # Fast fail the script on failures. 10 | set -ex 11 | 12 | # The directory that this script is located in. 13 | TOOL_DIR=`dirname "$0"` 14 | 15 | cd $TOOL_DIR/../pkgs/leak_tracker 16 | dart pub publish 17 | cd - 18 | 19 | cd $TOOL_DIR/../pkgs/leak_tracker_testing 20 | dart pub publish 21 | cd - 22 | 23 | cd $TOOL_DIR/../pkgs/leak_tracker_flutter_testing 24 | flutter pub publish 25 | cd - 26 | -------------------------------------------------------------------------------- /tool/pub_get.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2023 The Chromium Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style license that can be 5 | # found in the LICENSE file. 6 | 7 | # Runs `pub get` for all code in the repo. 8 | 9 | # Fast fail the script on failures. 10 | set -ex 11 | 12 | # The directory that this script is located in. 13 | TOOL_DIR=`dirname "$0"` 14 | 15 | cd $TOOL_DIR/../examples/autosnapshotting 16 | flutter pub get 17 | cd - 18 | 19 | cd $TOOL_DIR/../examples/leak_tracking 20 | flutter pub get 21 | cd - 22 | 23 | cd $TOOL_DIR/../pkgs/leak_tracker 24 | dart pub get 25 | cd - 26 | 27 | cd $TOOL_DIR/../pkgs/leak_tracker_flutter_testing 28 | flutter pub get 29 | cd - 30 | 31 | cd $TOOL_DIR/../pkgs/leak_tracker_testing 32 | flutter pub get 33 | cd - 34 | 35 | cd $TOOL_DIR/../pkgs/leak_tracker_web_tests 36 | flutter pub get 37 | cd - 38 | 39 | cd $TOOL_DIR/../pkgs/memory_usage 40 | dart pub get 41 | cd - 42 | --------------------------------------------------------------------------------