From bb6d1a2c247347c4991ae2b4a6835338ea90d665 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 27 Sep 2023 09:51:02 +0200 Subject: [PATCH 01/66] [PLAT-10781] Set up the Flutter Performance package (#1) * [PLAT-10781] Set up the Flutter Performance package * fix(android): added the profile buildType, and removed redundant @NonNull annotations * Fixed Makefile --------- Co-authored-by: Robert Co-authored-by: jason --- .buildkite/pipeline.yml | 32 + .github/ISSUE_TEMPLATE/A.md | 14 + .github/ISSUE_TEMPLATE/bug_report.md | 55 ++ .github/ISSUE_TEMPLATE/feature_request.md | 24 + .github/PULL_REQUEST_TEMPLATE.md | 16 + .github/support.md | 27 + .gitignore | 4 + CHANGELOG.md | 6 + CONTRIBUTING.md | 64 ++ Gemfile | 11 + Gemfile.lock | 126 +++ LICENSE | 21 + Makefile | 33 + VERSION | 1 + docker-compose.yml | 31 + docs/RELEASING.md | 0 .../bugsnag_performance_example/.gitignore | 52 ++ .../bugsnag_performance_example/.metadata | 45 ++ .../analysis_options.yaml | 1 + .../android/.gitignore | 13 + .../android/app/build.gradle | 72 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 33 + .../MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../android/build.gradle | 31 + .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/settings.gradle | 11 + .../ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../ios/Flutter/Debug.xcconfig | 2 + .../ios/Flutter/Release.xcconfig | 2 + .../bugsnag_performance_example/ios/Podfile | 44 ++ .../ios/Runner.xcodeproj/project.pbxproj | 724 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../ios/Runner/Info.plist | 51 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/RunnerTests/RunnerTests.swift | 12 + .../bugsnag_performance_example/lib/main.dart | 22 + .../bugsnag_performance_example/pubspec.yaml | 87 +++ .../bugsnag_flutter_performance/.gitignore | 29 + .../bugsnag_flutter_performance/.metadata | 33 + .../bugsnag_flutter_performance/CHANGELOG.md | 3 + packages/bugsnag_flutter_performance/LICENSE | 1 + .../bugsnag_flutter_performance/README.md | 15 + .../analysis_options.yaml | 4 + .../android/.gitignore | 9 + .../android/build.gradle | 70 ++ .../android/gradle.properties | 1 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/gradlew | 160 ++++ .../android/gradlew.bat | 90 +++ .../android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + .../BugsnagFlutterPerformancePlugin.kt | 33 + .../BugsnagFlutterPerformancePluginTest.kt | 27 + .../example/.gitignore | 44 ++ .../example/README.md | 16 + .../example/analysis_options.yaml | 29 + .../example/android/.gitignore | 13 + .../example/android/app/build.gradle | 72 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 33 + .../MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 31 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../example/android/settings.gradle | 11 + .../plugin_integration_test.dart | 25 + .../example/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../example/ios/Podfile | 44 ++ .../ios/Runner.xcodeproj/project.pbxproj | 616 +++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 51 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/ios/RunnerTests/RunnerTests.swift | 26 + .../example/lib/main.dart | 63 ++ .../example/pubspec.yaml | 85 ++ .../example/test/widget_test.dart | 27 + .../ios/.gitignore | 38 + .../ios/Assets/.gitkeep | 0 .../BugsnagFlutterPerformancePlugin.swift | 19 + .../ios/bugsnag_flutter_performance.podspec | 23 + .../lib/bugsnag_flutter_performance.dart | 7 + ...ag_flutter_performance_method_channel.dart | 17 + ...lutter_performance_platform_interface.dart | 29 + .../bugsnag_flutter_performance/pubspec.yaml | 72 ++ ...utter_performance_method_channel_test.dart | 27 + .../bugsnag_flutter_performance_test.dart | 29 + 173 files changed, 4643 insertions(+) create mode 100644 .buildkite/pipeline.yml create mode 100644 .github/ISSUE_TEMPLATE/A.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/support.md create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 VERSION create mode 100644 docker-compose.yml create mode 100644 docs/RELEASING.md create mode 100644 examples/bugsnag_performance_example/.gitignore create mode 100644 examples/bugsnag_performance_example/.metadata create mode 100644 examples/bugsnag_performance_example/analysis_options.yaml create mode 100644 examples/bugsnag_performance_example/android/.gitignore create mode 100644 examples/bugsnag_performance_example/android/app/build.gradle create mode 100644 examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml create mode 100644 examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml create mode 100644 examples/bugsnag_performance_example/android/build.gradle create mode 100644 examples/bugsnag_performance_example/android/gradle.properties create mode 100644 examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 examples/bugsnag_performance_example/android/settings.gradle create mode 100644 examples/bugsnag_performance_example/ios/.gitignore create mode 100644 examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig create mode 100644 examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig create mode 100644 examples/bugsnag_performance_example/ios/Podfile create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 examples/bugsnag_performance_example/ios/Runner/Info.plist create mode 100644 examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h create mode 100644 examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift create mode 100644 examples/bugsnag_performance_example/lib/main.dart create mode 100644 examples/bugsnag_performance_example/pubspec.yaml create mode 100644 packages/bugsnag_flutter_performance/.gitignore create mode 100644 packages/bugsnag_flutter_performance/.metadata create mode 100644 packages/bugsnag_flutter_performance/CHANGELOG.md create mode 100644 packages/bugsnag_flutter_performance/LICENSE create mode 100644 packages/bugsnag_flutter_performance/README.md create mode 100644 packages/bugsnag_flutter_performance/analysis_options.yaml create mode 100644 packages/bugsnag_flutter_performance/android/.gitignore create mode 100644 packages/bugsnag_flutter_performance/android/build.gradle create mode 100644 packages/bugsnag_flutter_performance/android/gradle.properties create mode 100755 packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 packages/bugsnag_flutter_performance/android/gradlew create mode 100755 packages/bugsnag_flutter_performance/android/gradlew.bat create mode 100644 packages/bugsnag_flutter_performance/android/settings.gradle create mode 100644 packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml create mode 100644 packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt create mode 100644 packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt create mode 100644 packages/bugsnag_flutter_performance/example/.gitignore create mode 100644 packages/bugsnag_flutter_performance/example/README.md create mode 100644 packages/bugsnag_flutter_performance/example/analysis_options.yaml create mode 100644 packages/bugsnag_flutter_performance/example/android/.gitignore create mode 100644 packages/bugsnag_flutter_performance/example/android/app/build.gradle create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/bugsnag_flutter_performance/example/android/build.gradle create mode 100644 packages/bugsnag_flutter_performance/example/android/gradle.properties create mode 100644 packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/bugsnag_flutter_performance/example/android/settings.gradle create mode 100644 packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart create mode 100644 packages/bugsnag_flutter_performance/example/ios/.gitignore create mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig create mode 100644 packages/bugsnag_flutter_performance/example/ios/Podfile create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist create mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift create mode 100644 packages/bugsnag_flutter_performance/example/lib/main.dart create mode 100644 packages/bugsnag_flutter_performance/example/pubspec.yaml create mode 100644 packages/bugsnag_flutter_performance/example/test/widget_test.dart create mode 100644 packages/bugsnag_flutter_performance/ios/.gitignore create mode 100644 packages/bugsnag_flutter_performance/ios/Assets/.gitkeep create mode 100644 packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift create mode 100644 packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec create mode 100644 packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart create mode 100644 packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart create mode 100644 packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart create mode 100644 packages/bugsnag_flutter_performance/pubspec.yaml create mode 100644 packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart create mode 100644 packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 0000000..207029f --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,32 @@ +agents: + queue: macos-12-arm + +steps: + + - label: ":test_tube: 3.10.0" + timeout_in_minutes: 10 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + commands: + - make test + + - label: ":lint-roller: 3.10.0" + timeout_in_minutes: 10 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + commands: + - make lint + + - label: Build Example App 3.10.0 + timeout_in_minutes: 10 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + commands: + - pod repo update + - make examples/flutter + # Verify that App.framework's UUID (and therefore our `codeIdentifier`) changes when Dart code is touched + - dwarfdump --arch=arm64 --uuid examples/flutter/build/ios/iphoneos/Runner.app/Frameworks/App.framework/App | tee uuid_before + - sed -i '' -e 's/add_your_api_key_here/my_api_key/' examples/flutter/lib/main.dart + - make examples/flutter + - dwarfdump --arch=arm64 --uuid examples/flutter/build/ios/iphoneos/Runner.app/Frameworks/App.framework/App | tee uuid_after + - test "$(cat uuid_before)" != "$(cat uuid_after)" diff --git a/.github/ISSUE_TEMPLATE/A.md b/.github/ISSUE_TEMPLATE/A.md new file mode 100644 index 0000000..e20813c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/A.md @@ -0,0 +1,14 @@ +--- +name: Having trouble getting started? +about: Please contact us at support@bugsnag.com for assistance with integrating Bugsnag + into your application. +title: '' +labels: '' +assignees: '' + +--- +Please checkout our [documentation](https://docs.bugsnag.com/performance/flutter/) for guides, references and tutorials. + +If you have questions about your integration please contact us at [support@bugsnag.com](mailto:support@bugsnag.com). + +Alternatively, view additional options at [support.md](../SUPPORT.md). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..287e098 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,55 @@ +--- +name: Bug report +about: Create a report to help us improve the library +title: "" +labels: "" +assignees: "" +--- + + + +### Describe the bug + +A clear and concise description of what the bug is. + +### Steps to reproduce + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +### Environment + +- BugSnag Performance version: +- BugSnag version (if any): +- Flutter version: +- Flutter-first app or native app with Flutter component(s): +- iOS/Android version: +- Simulator/emulator or physical device: + + + +### Example Repo + +- [ ] Create a minimal repository that can reproduce the issue +- [ ] Link to it here: + +### Example code snippet + +``` +# (Insert code sample to reproduce the problem) +``` + + +
Error messages: + +``` + +``` + +
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..cade96e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + + + +### Description + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f1e593d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## Goal + + + +## Design + + + +## Changeset + + + +## Testing + + \ No newline at end of file diff --git a/.github/support.md b/.github/support.md new file mode 100644 index 0000000..12be535 --- /dev/null +++ b/.github/support.md @@ -0,0 +1,27 @@ +## Are you having trouble getting started? + +If you haven't already, please checkout our [documentation](https://docs.bugsnag.com/performance/flutter/) for guides, references and tutorials. + +Or, if you wish you can [contact us directly](mailto:support@bugsnag.com) for assistance on integrating BugSnag into your application, troubleshooting an issue or a question about our supported features. + +When contacting support, please include as much information as necessary, including: + +- example code snippet +- steps to reproduce +- expected/actual behaviour + +- BugSnag Performance version: +- BugSnag version (if any): +- Flutter version: +- Flutter-first app or native app with Flutter component(s): +- iOS/Android version: +- Simulator/emulator or physical device: + +## Bug or Feature Requests + +If you would like to raise a bug or feature request please do so by creating a [New Issue](https://github.com/bugsnag/bugsnag-flutter-performance/issues/new/choose) and selecting bug or feature. +Please note: we cannot promise that we will fulfil all requests + +## Pull Requests + +If you have made a fix and would like to raise a pull request, please read our [CONTRIBUTING.md](../CONTRIBUTING.md) file before creating the pull request. diff --git a/.gitignore b/.gitignore index 3a83c2f..981f525 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ doc/api/ .flutter-plugins .flutter-plugins-dependencies + +Package.resolved +Podfile.lock +maze_output/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2c3a2cd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +Changelog +========= + +## TBD + +Initial preview release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..810ce60 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +Contributing +============ + +- [Fork](https://help.github.com/articles/fork-a-repo) + the [notifier on github](https://github.com/bugsnag/bugsnag-flutter-performance) +- Build and test your changes +- Commit and push until you are happy with your contribution +- [Make a pull request](https://help.github.com/articles/using-pull-requests) +- Thanks! + +## Reporting issues + +Are you having trouble getting started? +Please [contact us directly](mailto:support@bugsnag.com?subject=%5BGitHub%5D%20Flutter%20Performance%20SDK%20-%20having%20trouble%20getting%20started%20with%20Bugsnag) +for assistance with integrating Bugsnag into your application. If you have spotted a problem with +this module, feel free to open a [new issue](https://github.com/bugsnag/bugsnag-flutter-performance/issues/new). +Here are a few things to check before doing so: + +* Are you using the latest version of `bugsnag-flutter-performance`? If not, does updating to the latest version + fix your issue? +* Has somebody + else [already reported](https://github.com/bugsnag/bugsnag-flutter-performance/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen) + your issue? Feel free to add additional context to or check-in on an existing issue that matches + your own. +* Is your issue caused by this module? Only things related to the `bugsnag-flutter-performance` module should be + reported here. For anything else, please [contact us directly](mailto:support@bugsnag.com) and + we'd be happy to help you out. + +### Fixing issues + +If you've identified a fix to a new or existing issue, we welcome contributions! +Here are some helpful suggestions on contributing that help us merge your PR quickly and smoothly: + +* [Fork](https://help.github.com/articles/fork-a-repo) the + [library on GitHub](https://github.com/bugsnag/bugsnag-flutter-performance) +* Build and test your changes using the example app and test suite +* Commit and push until you are happy with your contribution +* [Make a pull request](https://help.github.com/articles/using-pull-requests) +* Ensure the automated checks pass (and if it fails, please try to address the cause) + +### Adding features + +Unfortunately we’re unable to accept PRs that add features or refactor the library at this time. +However, we’re very eager and welcome to hearing feedback about the library so please contact us +directly to discuss your idea, or open a +[feature request](https://github.com/bugsnag/bugsnag-flutter-performance/issues/new?template=Feature_request.md) +to help us improve the library. + +Here’s a bit about our process designing and building the Bugsnag libraries: + +* We have an internal roadmap to plan out the features we build, and sometimes we will already be + planning your suggested feature! +* Our open source libraries span many languages and frameworks so we strive to ensure they are + idiomatic on the given platform, but also consistent in terminology between platforms. That way + the core concepts are familiar whether you adopt Bugsnag for one platform or many. +* Finally, one of our goals is to ensure our libraries work reliably, even in crashy, multi-threaded + environments. Oftentimes, this requires an intensive engineering design and code review process + that adheres to our style and linting guidelines. + +## Further development docs + +For information on how to build the library and develop changes you should start by +reading [the docs](docs/RELEASING.md). + diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..cadf196 --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +source 'https://rubygems.org' + +# A reference to Maze Runner is only needed for running tests locally and if committed it must be +# portable for CI, e.g. a specific release. However, leaving it commented out would mean quicker CI. +gem 'bugsnag-maze-runner', '~> 8.0' + +# Use a specific branch +#gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'master' + +# Locally, you can run against Maze Runner branches and uncommitted changes: +#gem 'bugsnag-maze-runner', path: '../maze-runner' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..4441577 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,126 @@ +GEM + remote: https://rubygems.org/ + specs: + appium_lib (12.0.1) + appium_lib_core (~> 5.0) + nokogiri (~> 1.8, >= 1.8.1) + tomlrb (>= 1.1, < 3.0) + appium_lib_core (5.4.0) + faye-websocket (~> 0.11.0) + selenium-webdriver (~> 4.2, < 4.6) + bugsnag (6.26.0) + concurrent-ruby (~> 1.0) + bugsnag-maze-runner (8.2.0) + appium_lib (~> 12.0.0) + appium_lib_core (~> 5.4.0) + bugsnag (~> 6.24) + cucumber (~> 7.1) + cucumber-expressions (~> 6.0.0) + curb (~> 0.9.6) + dogstatsd-ruby (~> 5.5.0) + json_schemer (~> 0.2.24) + optimist (~> 3.0.1) + os (~> 1.0.0) + rack (~> 2.2) + rake (~> 12.3.3) + rubyzip (~> 2.3.2) + selenium-webdriver (~> 4.0) + test-unit (~> 3.5.2) + webrick (~> 1.7.0) + builder (3.2.4) + childprocess (4.1.0) + concurrent-ruby (1.2.2) + cucumber (7.1.0) + builder (~> 3.2, >= 3.2.4) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-create-meta (~> 6.0, >= 6.0.1) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-html-formatter (~> 17.0, >= 17.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-wire (~> 6.2, >= 6.2.0) + diff-lcs (~> 1.4, >= 1.4.4) + mime-types (~> 3.3, >= 3.3.1) + multi_test (~> 0.1, >= 0.1.2) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-core (10.1.1) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) + cucumber-create-meta (6.0.4) + cucumber-messages (~> 17.1, >= 17.1.1) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-cucumber-expressions (14.0.0) + cucumber-expressions (6.0.1) + cucumber-gherkin (22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-html-formatter (17.0.0) + cucumber-messages (~> 17.1, >= 17.1.0) + cucumber-messages (17.1.1) + cucumber-tag-expressions (4.1.0) + cucumber-wire (6.2.1) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + curb (0.9.11) + diff-lcs (1.5.0) + dogstatsd-ruby (5.5.0) + ecma-re-validator (0.4.0) + regexp_parser (~> 2.2) + eventmachine (1.2.7) + faye-websocket (0.11.3) + eventmachine (>= 0.12.0) + websocket-driver (>= 0.5.1) + ffi (1.15.5) + hana (1.3.7) + json_schemer (0.2.25) + ecma-re-validator (~> 0.3) + hana (~> 1.3) + regexp_parser (~> 2.0) + simpleidn (~> 0.2) + uri_template (~> 0.7) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2023.0218.1) + multi_test (0.1.2) + nokogiri (1.15.3-x86_64-darwin) + racc (~> 1.4) + optimist (3.0.1) + os (1.0.1) + power_assert (2.0.3) + racc (1.7.1) + rack (2.2.8) + rake (12.3.3) + regexp_parser (2.8.1) + rexml (3.2.6) + rubyzip (2.3.2) + selenium-webdriver (4.5.0) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + simpleidn (0.2.1) + unf (~> 0.1.4) + sys-uname (1.2.3) + ffi (~> 1.1) + test-unit (3.5.9) + power_assert + tomlrb (2.0.3) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + uri_template (0.7.0) + webrick (1.7.0) + websocket (1.2.9) + websocket-driver (0.7.6) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + +PLATFORMS + x86_64-darwin-20 + x86_64-darwin-21 + +DEPENDENCIES + bugsnag-maze-runner (~> 8.0) + +BUNDLED WITH + 2.4.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e578c23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Bugsnag + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..28d5cc8 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +FLUTTER_BIN?=flutter + +all: format build lint test + +.PHONY: clean build bump aar examples/bugsnag_performance_example test format lint e2e_android_local e2e_ios_local + +clean: + cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) clean --suppress-analytics + cd examples/bugsnag_performance_example && $(FLUTTER_BIN) clean --suppress-analytics && \ + rm -rf .idea bugsnag_flutter_performance_example.iml \ + ios/{Pods,.symlinks,Podfile.lock} \ + ios/{Runner.xcworkspace,Runner.xcodeproj,Runner.xcodeproj/project.xcworkspace}/xcuserdata \ + android/{.idea,.gradle,gradlew,gradlew.bat,local.properties,bugsnag_flutter_performance_example_android.iml} + rm -rf staging + +build: aar examples/bugsnag_performance_example + +aar: + cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) build aar --suppress-analytics + +examples/bugsnag_performance_example: + cd $@ && $(FLUTTER_BIN) pub get + cd $@ && $(FLUTTER_BIN) build apk --suppress-analytics --no-tree-shake-icons + cd $@ && $(FLUTTER_BIN) build ios --no-codesign --suppress-analytics --no-tree-shake-icons + +test: + cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) test -r expanded --suppress-analytics + +format: + $(FLUTTER_BIN) format packages/bugsnag_flutter_performance example features/fixtures/app + +lint: + cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) analyze --suppress-analytics diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3fac688 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3.6' +services: + + maze-runner: + image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v8-cli + environment: + DEBUG: + BITBAR_USERNAME: + BITBAR_ACCESS_KEY: + BUILDKITE: + BUILDKITE_BRANCH: + BUILDKITE_BUILD_CREATOR: + BUILDKITE_BUILD_NUMBER: + BUILDKITE_BUILD_URL: + BUILDKITE_LABEL: + BUILDKITE_MESSAGE: + BUILDKITE_PIPELINE_NAME: + BUILDKITE_REPO: + BUILDKITE_RETRY_COUNT: + BUILDKITE_STEP_KEY: + MAZE_BUGSNAG_API_KEY: + ports: + - "9000-9499:9339" + volumes: + - ./features/:/app/features/ + - ./maze_output:/app/maze_output + - /var/run/docker.sock:/var/run/docker.sock + +networks: + default: + name: ${BUILDKITE_JOB_ID:-android-maze-runner} diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/bugsnag_performance_example/.gitignore b/examples/bugsnag_performance_example/.gitignore new file mode 100644 index 0000000..b19fdb0 --- /dev/null +++ b/examples/bugsnag_performance_example/.gitignore @@ -0,0 +1,52 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Xcode will place build artifacts here +/ios/Build + +# Don't lock dependencies for our example +pubspec.lock \ No newline at end of file diff --git a/examples/bugsnag_performance_example/.metadata b/examples/bugsnag_performance_example/.metadata new file mode 100644 index 0000000..e94891d --- /dev/null +++ b/examples/bugsnag_performance_example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: android + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: ios + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: linux + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: macos + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: web + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: windows + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/examples/bugsnag_performance_example/analysis_options.yaml b/examples/bugsnag_performance_example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/examples/bugsnag_performance_example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/examples/bugsnag_performance_example/android/.gitignore b/examples/bugsnag_performance_example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/examples/bugsnag_performance_example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/examples/bugsnag_performance_example/android/app/build.gradle b/examples/bugsnag_performance_example/android/app/build.gradle new file mode 100644 index 0000000..e8793b0 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/build.gradle @@ -0,0 +1,72 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.example.bugsnag_performance_example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.bugsnag_performance_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml b/examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml b/examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b4cbff2 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt b/examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt new file mode 100644 index 0000000..3d063b6 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.bugsnag_performance_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml b/examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml b/examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml b/examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml b/examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml b/examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/bugsnag_performance_example/android/build.gradle b/examples/bugsnag_performance_example/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/examples/bugsnag_performance_example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/examples/bugsnag_performance_example/android/gradle.properties b/examples/bugsnag_performance_example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/examples/bugsnag_performance_example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties b/examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/examples/bugsnag_performance_example/android/settings.gradle b/examples/bugsnag_performance_example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/examples/bugsnag_performance_example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/examples/bugsnag_performance_example/ios/.gitignore b/examples/bugsnag_performance_example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist b/examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig b/examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig b/examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/examples/bugsnag_performance_example/ios/Podfile b/examples/bugsnag_performance_example/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a07cf1b --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,724 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5FA05D144379C494AAD1C98E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0998009573D03AB22F89F4FE /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7BCC4926EBE7AB02564AEF38 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D452F6E556B93FC9C76CBDB /* Pods_RunnerTests.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0998009573D03AB22F89F4FE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17BEA007711D5700BF3B7C87 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1F109DBD605BB0393EFE4C5C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3BD8C89E201634C0D4F8621F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 4870107A7024782AB573E5F2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4B23C885C6FB41F2A7CF2C44 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7D452F6E556B93FC9C76CBDB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D8394254CC22B728F1BF3A4C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8E3AFFBBD169274737EDDEF4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7BCC4926EBE7AB02564AEF38 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5FA05D144379C494AAD1C98E /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9369B4E767306A566E7477A2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0998009573D03AB22F89F4FE /* Pods_Runner.framework */, + 7D452F6E556B93FC9C76CBDB /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + A8B98F29284511887CA1EBC0 /* Pods */, + 9369B4E767306A566E7477A2 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A8B98F29284511887CA1EBC0 /* Pods */ = { + isa = PBXGroup; + children = ( + 1F109DBD605BB0393EFE4C5C /* Pods-Runner.debug.xcconfig */, + 17BEA007711D5700BF3B7C87 /* Pods-Runner.release.xcconfig */, + D8394254CC22B728F1BF3A4C /* Pods-Runner.profile.xcconfig */, + 4870107A7024782AB573E5F2 /* Pods-RunnerTests.debug.xcconfig */, + 3BD8C89E201634C0D4F8621F /* Pods-RunnerTests.release.xcconfig */, + 4B23C885C6FB41F2A7CF2C44 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 58452C8725F52B6B30AFFBC6 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 8E3AFFBBD169274737EDDEF4 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + B1A9980D008DD9059D1D6E60 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 89A21DBF6C43ACE69A6498F3 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 58452C8725F52B6B30AFFBC6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 89A21DBF6C43ACE69A6498F3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B1A9980D008DD9059D1D6E60 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4870107A7024782AB573E5F2 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3BD8C89E201634C0D4F8621F /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4B23C885C6FB41F2A7CF2C44 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift b/examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard b/examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/bugsnag_performance_example/ios/Runner/Info.plist b/examples/bugsnag_performance_example/ios/Runner/Info.plist new file mode 100644 index 0000000..2d5083c --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Bugsnag Performance Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + bugsnag_performance_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h b/examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift b/examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart new file mode 100644 index 0000000..e479536 --- /dev/null +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +const apiKey = 'add_your_api_key_here'; + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: Text('Hello World!'), + ), + ), + ); + } +} diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml new file mode 100644 index 0000000..3fcb6b5 --- /dev/null +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -0,0 +1,87 @@ +name: bugsnag_performance_example +version: 1.0.0 +description: Demonstrates how to use the bugsnag_flutter_performance package. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=3.0.0 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + bugsnag_flutter_performance: + # When depending on this package from a real application you should use: + # flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../../packages/bugsnag_flutter_performance + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + + http: ^0.13.4 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/bugsnag_flutter_performance/.gitignore b/packages/bugsnag_flutter_performance/.gitignore new file mode 100644 index 0000000..9be145f --- /dev/null +++ b/packages/bugsnag_flutter_performance/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/bugsnag_flutter_performance/.metadata b/packages/bugsnag_flutter_performance/.metadata new file mode 100644 index 0000000..9013972 --- /dev/null +++ b/packages/bugsnag_flutter_performance/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: android + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: ios + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/bugsnag_flutter_performance/CHANGELOG.md b/packages/bugsnag_flutter_performance/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/packages/bugsnag_flutter_performance/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/bugsnag_flutter_performance/LICENSE b/packages/bugsnag_flutter_performance/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/packages/bugsnag_flutter_performance/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/bugsnag_flutter_performance/README.md b/packages/bugsnag_flutter_performance/README.md new file mode 100644 index 0000000..6498765 --- /dev/null +++ b/packages/bugsnag_flutter_performance/README.md @@ -0,0 +1,15 @@ +# bugsnag_flutter_performance + +A new Flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/packages/bugsnag_flutter_performance/analysis_options.yaml b/packages/bugsnag_flutter_performance/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/bugsnag_flutter_performance/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/bugsnag_flutter_performance/android/.gitignore b/packages/bugsnag_flutter_performance/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/packages/bugsnag_flutter_performance/android/build.gradle b/packages/bugsnag_flutter_performance/android/build.gradle new file mode 100644 index 0000000..bc14fc3 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/build.gradle @@ -0,0 +1,70 @@ +group 'com.example.bugsnag_flutter_performance' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 16 + } + + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + buildTypes { + profile { + initWith debug + } + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/packages/bugsnag_flutter_performance/android/gradle.properties b/packages/bugsnag_flutter_performance/android/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar b/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties b/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/bugsnag_flutter_performance/android/gradlew b/packages/bugsnag_flutter_performance/android/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/packages/bugsnag_flutter_performance/android/gradlew.bat b/packages/bugsnag_flutter_performance/android/gradlew.bat new file mode 100755 index 0000000..aec9973 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/bugsnag_flutter_performance/android/settings.gradle b/packages/bugsnag_flutter_performance/android/settings.gradle new file mode 100644 index 0000000..602593a --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'bugsnag_flutter_performance' diff --git a/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml b/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bb5472c --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt b/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt new file mode 100644 index 0000000..ac75c60 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt @@ -0,0 +1,33 @@ +package com.example.bugsnag_flutter_performance + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** BugsnagFlutterPerformancePlugin */ +class BugsnagFlutterPerformancePlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "bugsnag_flutter_performance") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt b/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt new file mode 100644 index 0000000..716d866 --- /dev/null +++ b/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt @@ -0,0 +1,27 @@ +package com.example.bugsnag_flutter_performance + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class BugsnagFlutterPerformancePluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = BugsnagFlutterPerformancePlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/packages/bugsnag_flutter_performance/example/.gitignore b/packages/bugsnag_flutter_performance/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/bugsnag_flutter_performance/example/README.md b/packages/bugsnag_flutter_performance/example/README.md new file mode 100644 index 0000000..a5cc4e1 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/README.md @@ -0,0 +1,16 @@ +# bugsnag_flutter_performance_example + +Demonstrates how to use the bugsnag_flutter_performance plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/bugsnag_flutter_performance/example/analysis_options.yaml b/packages/bugsnag_flutter_performance/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/bugsnag_flutter_performance/example/android/.gitignore b/packages/bugsnag_flutter_performance/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/bugsnag_flutter_performance/example/android/app/build.gradle b/packages/bugsnag_flutter_performance/example/android/app/build.gradle new file mode 100644 index 0000000..427a3eb --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/build.gradle @@ -0,0 +1,72 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.example.bugsnag_flutter_performance_example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.bugsnag_flutter_performance_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7a7d24a --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt b/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt new file mode 100644 index 0000000..66d665a --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.bugsnag_flutter_performance_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/bugsnag_flutter_performance/example/android/build.gradle b/packages/bugsnag_flutter_performance/example/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/bugsnag_flutter_performance/example/android/gradle.properties b/packages/bugsnag_flutter_performance/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/bugsnag_flutter_performance/example/android/settings.gradle b/packages/bugsnag_flutter_performance/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart b/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..0648a5e --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,25 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://docs.flutter.dev/cookbook/testing/integration/introduction + + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('getPlatformVersion test', (WidgetTester tester) async { + final BugsnagFlutterPerformance plugin = BugsnagFlutterPerformance(); + final String? version = await plugin.getPlatformVersion(); + // The version string depends on the host platform running the test, so + // just assert that some non-empty string is returned. + expect(version?.isNotEmpty, true); + }); +} diff --git a/packages/bugsnag_flutter_performance/example/ios/.gitignore b/packages/bugsnag_flutter_performance/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist b/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig b/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig b/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/bugsnag_flutter_performance/example/ios/Podfile b/packages/bugsnag_flutter_performance/example/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..aea275c --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 78GCGX8XL2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e42adcb --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift b/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist b/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist new file mode 100644 index 0000000..cec5d02 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Bugsnag Flutter Performance + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + bugsnag_flutter_performance_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h b/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift b/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..e591c89 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,26 @@ +import Flutter +import UIKit +import XCTest + +@testable import bugsnag_flutter_performance + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = BugsnagFlutterPerformancePlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/packages/bugsnag_flutter_performance/example/lib/main.dart b/packages/bugsnag_flutter_performance/example/lib/main.dart new file mode 100644 index 0000000..b3b35a3 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/lib/main.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _bugsnagFlutterPerformancePlugin = BugsnagFlutterPerformance(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + platformVersion = + await _bugsnagFlutterPerformancePlugin.getPlatformVersion() ?? 'Unknown platform version'; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} diff --git a/packages/bugsnag_flutter_performance/example/pubspec.yaml b/packages/bugsnag_flutter_performance/example/pubspec.yaml new file mode 100644 index 0000000..3ad0a47 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: bugsnag_flutter_performance_example +description: Demonstrates how to use the bugsnag_flutter_performance plugin. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=3.0.0 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + bugsnag_flutter_performance: + # When depending on this package from a real application you should use: + # bugsnag_flutter_performance: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/bugsnag_flutter_performance/example/test/widget_test.dart b/packages/bugsnag_flutter_performance/example/test/widget_test.dart new file mode 100644 index 0000000..284e875 --- /dev/null +++ b/packages/bugsnag_flutter_performance/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:bugsnag_flutter_performance_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/packages/bugsnag_flutter_performance/ios/.gitignore b/packages/bugsnag_flutter_performance/ios/.gitignore new file mode 100644 index 0000000..0c88507 --- /dev/null +++ b/packages/bugsnag_flutter_performance/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/ios/Assets/.gitkeep b/packages/bugsnag_flutter_performance/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift b/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift new file mode 100644 index 0000000..6551949 --- /dev/null +++ b/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift @@ -0,0 +1,19 @@ +import Flutter +import UIKit + +public class BugsnagFlutterPerformancePlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "bugsnag_flutter_performance", binaryMessenger: registrar.messenger()) + let instance = BugsnagFlutterPerformancePlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec b/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec new file mode 100644 index 0000000..afe523a --- /dev/null +++ b/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint bugsnag_flutter_performance.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'bugsnag_flutter_performance' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '11.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart new file mode 100644 index 0000000..5d55ee5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -0,0 +1,7 @@ +library bugsnag_flutter_performance; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart new file mode 100644 index 0000000..2d45544 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'bugsnag_flutter_performance_platform_interface.dart'; + +/// An implementation of [BugsnagFlutterPerformancePlatform] that uses method channels. +class MethodChannelBugsnagFlutterPerformance extends BugsnagFlutterPerformancePlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('bugsnag_flutter_performance'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart new file mode 100644 index 0000000..9141660 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'bugsnag_flutter_performance_method_channel.dart'; + +abstract class BugsnagFlutterPerformancePlatform extends PlatformInterface { + /// Constructs a BugsnagFlutterPerformancePlatform. + BugsnagFlutterPerformancePlatform() : super(token: _token); + + static final Object _token = Object(); + + static BugsnagFlutterPerformancePlatform _instance = MethodChannelBugsnagFlutterPerformance(); + + /// The default instance of [BugsnagFlutterPerformancePlatform] to use. + /// + /// Defaults to [MethodChannelBugsnagFlutterPerformance]. + static BugsnagFlutterPerformancePlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [BugsnagFlutterPerformancePlatform] when + /// they register themselves. + static set instance(BugsnagFlutterPerformancePlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml new file mode 100644 index 0000000..7e4d843 --- /dev/null +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -0,0 +1,72 @@ +name: bugsnag_flutter_performance +description: A new Flutter plugin project. +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.example.bugsnag_flutter_performance + pluginClass: BugsnagFlutterPerformancePlugin + ios: + pluginClass: BugsnagFlutterPerformancePlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart new file mode 100644 index 0000000..68b589c --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelBugsnagFlutterPerformance platform = MethodChannelBugsnagFlutterPerformance(); + const MethodChannel channel = MethodChannel('bugsnag_flutter_performance'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart new file mode 100644 index 0000000..58b2883 --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_platform_interface.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockBugsnagFlutterPerformancePlatform + with MockPlatformInterfaceMixin + implements BugsnagFlutterPerformancePlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final BugsnagFlutterPerformancePlatform initialPlatform = BugsnagFlutterPerformancePlatform.instance; + + test('$MethodChannelBugsnagFlutterPerformance is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + BugsnagFlutterPerformance bugsnagFlutterPerformancePlugin = BugsnagFlutterPerformance(); + MockBugsnagFlutterPerformancePlatform fakePlatform = MockBugsnagFlutterPerformancePlatform(); + BugsnagFlutterPerformancePlatform.instance = fakePlatform; + + expect(await bugsnagFlutterPerformancePlugin.getPlatformVersion(), '42'); + }); +} From a31d688ca21a32aaf6f5ee90de5207ed236d318c Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:31:12 +0200 Subject: [PATCH 02/66] Initial interfaces (#2) * Removed auto-generated example from bugsnag_flutter_performance package * [PLAT-10783] Created client, span and configuration classes * Formatting and interface improvements * Update packages/bugsnag_flutter_performance/lib/src/span.dart Co-authored-by: Jason * Fixed formatting --------- Co-authored-by: Robert Co-authored-by: Jason --- .../example/.gitignore | 44 -- .../example/README.md | 16 - .../example/analysis_options.yaml | 29 - .../example/android/.gitignore | 13 - .../example/android/app/build.gradle | 72 -- .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 33 - .../MainActivity.kt | 6 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - .../example/android/build.gradle | 31 - .../example/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../example/android/settings.gradle | 11 - .../plugin_integration_test.dart | 25 - .../example/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 26 - .../example/ios/Flutter/Debug.xcconfig | 2 - .../example/ios/Flutter/Release.xcconfig | 2 - .../example/ios/Podfile | 44 -- .../ios/Runner.xcodeproj/project.pbxproj | 616 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 98 --- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../example/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 -- .../ios/Runner/Base.lproj/Main.storyboard | 26 - .../example/ios/Runner/Info.plist | 51 -- .../ios/Runner/Runner-Bridging-Header.h | 1 - .../example/ios/RunnerTests/RunnerTests.swift | 26 - .../example/lib/main.dart | 63 -- .../example/pubspec.yaml | 85 --- .../example/test/widget_test.dart | 27 - .../lib/bugsnag_flutter_performance.dart | 8 +- ...ag_flutter_performance_method_channel.dart | 6 +- ...lutter_performance_platform_interface.dart | 3 +- .../lib/src/client.dart | 24 + .../lib/src/configuration.dart | 5 + .../lib/src/span.dart | 19 + ...utter_performance_method_channel_test.dart | 27 - .../bugsnag_flutter_performance_test.dart | 29 - 74 files changed, 57 insertions(+), 1773 deletions(-) delete mode 100644 packages/bugsnag_flutter_performance/example/.gitignore delete mode 100644 packages/bugsnag_flutter_performance/example/README.md delete mode 100644 packages/bugsnag_flutter_performance/example/analysis_options.yaml delete mode 100644 packages/bugsnag_flutter_performance/example/android/.gitignore delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/build.gradle delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml delete mode 100644 packages/bugsnag_flutter_performance/example/android/build.gradle delete mode 100644 packages/bugsnag_flutter_performance/example/android/gradle.properties delete mode 100644 packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 packages/bugsnag_flutter_performance/example/android/settings.gradle delete mode 100644 packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart delete mode 100644 packages/bugsnag_flutter_performance/example/ios/.gitignore delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Podfile delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist delete mode 100644 packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h delete mode 100644 packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift delete mode 100644 packages/bugsnag_flutter_performance/example/lib/main.dart delete mode 100644 packages/bugsnag_flutter_performance/example/pubspec.yaml delete mode 100644 packages/bugsnag_flutter_performance/example/test/widget_test.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/client.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/configuration.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/span.dart delete mode 100644 packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart delete mode 100644 packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart diff --git a/packages/bugsnag_flutter_performance/example/.gitignore b/packages/bugsnag_flutter_performance/example/.gitignore deleted file mode 100644 index 24476c5..0000000 --- a/packages/bugsnag_flutter_performance/example/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/packages/bugsnag_flutter_performance/example/README.md b/packages/bugsnag_flutter_performance/example/README.md deleted file mode 100644 index a5cc4e1..0000000 --- a/packages/bugsnag_flutter_performance/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# bugsnag_flutter_performance_example - -Demonstrates how to use the bugsnag_flutter_performance plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/packages/bugsnag_flutter_performance/example/analysis_options.yaml b/packages/bugsnag_flutter_performance/example/analysis_options.yaml deleted file mode 100644 index 61b6c4d..0000000 --- a/packages/bugsnag_flutter_performance/example/analysis_options.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/packages/bugsnag_flutter_performance/example/android/.gitignore b/packages/bugsnag_flutter_performance/example/android/.gitignore deleted file mode 100644 index 6f56801..0000000 --- a/packages/bugsnag_flutter_performance/example/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/packages/bugsnag_flutter_performance/example/android/app/build.gradle b/packages/bugsnag_flutter_performance/example/android/app/build.gradle deleted file mode 100644 index 427a3eb..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - namespace "com.example.bugsnag_flutter_performance_example" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.bugsnag_flutter_performance_example" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 7a7d24a..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt b/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt deleted file mode 100644 index 66d665a..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/kotlin/com/example/bugsnag_flutter_performance_example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.bugsnag_flutter_performance_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml b/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml b/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/packages/bugsnag_flutter_performance/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/bugsnag_flutter_performance/example/android/build.gradle b/packages/bugsnag_flutter_performance/example/android/build.gradle deleted file mode 100644 index f7eb7f6..0000000 --- a/packages/bugsnag_flutter_performance/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/packages/bugsnag_flutter_performance/example/android/gradle.properties b/packages/bugsnag_flutter_performance/example/android/gradle.properties deleted file mode 100644 index 94adc3a..0000000 --- a/packages/bugsnag_flutter_performance/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3c472b9..0000000 --- a/packages/bugsnag_flutter_performance/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/bugsnag_flutter_performance/example/android/settings.gradle b/packages/bugsnag_flutter_performance/example/android/settings.gradle deleted file mode 100644 index 44e62bc..0000000 --- a/packages/bugsnag_flutter_performance/example/android/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart b/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart deleted file mode 100644 index 0648a5e..0000000 --- a/packages/bugsnag_flutter_performance/example/integration_test/plugin_integration_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -// This is a basic Flutter integration test. -// -// Since integration tests run in a full Flutter application, they can interact -// with the host side of a plugin implementation, unlike Dart unit tests. -// -// For more information about Flutter integration tests, please see -// https://docs.flutter.dev/cookbook/testing/integration/introduction - - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - testWidgets('getPlatformVersion test', (WidgetTester tester) async { - final BugsnagFlutterPerformance plugin = BugsnagFlutterPerformance(); - final String? version = await plugin.getPlatformVersion(); - // The version string depends on the host platform running the test, so - // just assert that some non-empty string is returned. - expect(version?.isNotEmpty, true); - }); -} diff --git a/packages/bugsnag_flutter_performance/example/ios/.gitignore b/packages/bugsnag_flutter_performance/example/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist b/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 9625e10..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 11.0 - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig b/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig b/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/packages/bugsnag_flutter_performance/example/ios/Podfile b/packages/bugsnag_flutter_performance/example/ios/Podfile deleted file mode 100644 index fdcc671..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Podfile +++ /dev/null @@ -1,44 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '11.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index aea275c..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,616 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C807D294A63A400263BE5 /* Sources */, - 331C807E294A63A400263BE5 /* Frameworks */, - 331C807F294A63A400263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1300; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 78GCGX8XL2; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 78GCGX8XL2; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 78GCGX8XL2; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bugsnagFlutterPerformanceExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index e42adcb..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a1..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift b/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard b/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist b/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist deleted file mode 100644 index cec5d02..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Info.plist +++ /dev/null @@ -1,51 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Bugsnag Flutter Performance - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - bugsnag_flutter_performance_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h b/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift b/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index e591c89..0000000 --- a/packages/bugsnag_flutter_performance/example/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Flutter -import UIKit -import XCTest - -@testable import bugsnag_flutter_performance - -// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. -// -// See https://developer.apple.com/documentation/xctest for more information about using XCTest. - -class RunnerTests: XCTestCase { - - func testGetPlatformVersion() { - let plugin = BugsnagFlutterPerformancePlugin() - - let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) - resultExpectation.fulfill() - } - waitForExpectations(timeout: 1) - } - -} diff --git a/packages/bugsnag_flutter_performance/example/lib/main.dart b/packages/bugsnag_flutter_performance/example/lib/main.dart deleted file mode 100644 index b3b35a3..0000000 --- a/packages/bugsnag_flutter_performance/example/lib/main.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - String _platformVersion = 'Unknown'; - final _bugsnagFlutterPerformancePlugin = BugsnagFlutterPerformance(); - - @override - void initState() { - super.initState(); - initPlatformState(); - } - - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - platformVersion = - await _bugsnagFlutterPerformancePlugin.getPlatformVersion() ?? 'Unknown platform version'; - } on PlatformException { - platformVersion = 'Failed to get platform version.'; - } - - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; - - setState(() { - _platformVersion = platformVersion; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $_platformVersion\n'), - ), - ), - ); - } -} diff --git a/packages/bugsnag_flutter_performance/example/pubspec.yaml b/packages/bugsnag_flutter_performance/example/pubspec.yaml deleted file mode 100644 index 3ad0a47..0000000 --- a/packages/bugsnag_flutter_performance/example/pubspec.yaml +++ /dev/null @@ -1,85 +0,0 @@ -name: bugsnag_flutter_performance_example -description: Demonstrates how to use the bugsnag_flutter_performance plugin. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -environment: - sdk: '>=3.0.0 <4.0.0' - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - bugsnag_flutter_performance: - # When depending on this package from a real application you should use: - # bugsnag_flutter_performance: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - integration_test: - sdk: flutter - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/bugsnag_flutter_performance/example/test/widget_test.dart b/packages/bugsnag_flutter_performance/example/test/widget_test.dart deleted file mode 100644 index 284e875..0000000 --- a/packages/bugsnag_flutter_performance/example/test/widget_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:bugsnag_flutter_performance_example/main.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); -} diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 5d55ee5..1e181d2 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -1,7 +1,5 @@ library bugsnag_flutter_performance; -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; -} +export 'src/client.dart'; +export 'src/configuration.dart'; +export 'src/span.dart' show BugsnagPerformanceSpan; diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart index 2d45544..3064daf 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart @@ -4,14 +4,16 @@ import 'package:flutter/services.dart'; import 'bugsnag_flutter_performance_platform_interface.dart'; /// An implementation of [BugsnagFlutterPerformancePlatform] that uses method channels. -class MethodChannelBugsnagFlutterPerformance extends BugsnagFlutterPerformancePlatform { +class MethodChannelBugsnagFlutterPerformance + extends BugsnagFlutterPerformancePlatform { /// The method channel used to interact with the native platform. @visibleForTesting final methodChannel = const MethodChannel('bugsnag_flutter_performance'); @override Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); + final version = + await methodChannel.invokeMethod('getPlatformVersion'); return version; } } diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart index 9141660..a404bfc 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart @@ -8,7 +8,8 @@ abstract class BugsnagFlutterPerformancePlatform extends PlatformInterface { static final Object _token = Object(); - static BugsnagFlutterPerformancePlatform _instance = MethodChannelBugsnagFlutterPerformance(); + static BugsnagFlutterPerformancePlatform _instance = + MethodChannelBugsnagFlutterPerformance(); /// The default instance of [BugsnagFlutterPerformancePlatform] to use. /// diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart new file mode 100644 index 0000000..7168533 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -0,0 +1,24 @@ +import 'configuration.dart'; +import 'span.dart'; + +abstract class BugsnagPerformance { + Future start({String? apiKey, Uri? endpoint}); + BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}); +} + +class BugsnagPerformanceClient implements BugsnagPerformance { + BugsnagPerformanceConfiguration? configuration; + @override + Future start({String? apiKey, Uri? endpoint}) async { + configuration = BugsnagPerformanceConfiguration( + apiKey: apiKey, + endpoint: endpoint, + ); + } + + @override + BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}) { + return BugsnagPerformanceSpanImpl( + name: name, startTime: startTime ?? DateTime.now()); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart new file mode 100644 index 0000000..8ed1a6d --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -0,0 +1,5 @@ +class BugsnagPerformanceConfiguration { + BugsnagPerformanceConfiguration({this.apiKey, this.endpoint}); + String? apiKey; + Uri? endpoint; +} diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart new file mode 100644 index 0000000..e4a653f --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -0,0 +1,19 @@ +abstract class BugsnagPerformanceSpan { + void end(); +} + +class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { + BugsnagPerformanceSpanImpl({required this.name, required this.startTime}); + final DateTime startTime; + final String name; + DateTime? endTime; + + @override + void end() { + if (endTime != null) { + return; + } + + endTime = DateTime.now(); + } +} diff --git a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart deleted file mode 100644 index 68b589c..0000000 --- a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_method_channel_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_method_channel.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - MethodChannelBugsnagFlutterPerformance platform = MethodChannelBugsnagFlutterPerformance(); - const MethodChannel channel = MethodChannel('bugsnag_flutter_performance'); - - setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - channel, - (MethodCall methodCall) async { - return '42'; - }, - ); - }); - - tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); - }); - - test('getPlatformVersion', () async { - expect(await platform.getPlatformVersion(), '42'); - }); -} diff --git a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart b/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart deleted file mode 100644 index 58b2883..0000000 --- a/packages/bugsnag_flutter_performance/test/bugsnag_flutter_performance_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_platform_interface.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance_method_channel.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockBugsnagFlutterPerformancePlatform - with MockPlatformInterfaceMixin - implements BugsnagFlutterPerformancePlatform { - - @override - Future getPlatformVersion() => Future.value('42'); -} - -void main() { - final BugsnagFlutterPerformancePlatform initialPlatform = BugsnagFlutterPerformancePlatform.instance; - - test('$MethodChannelBugsnagFlutterPerformance is the default instance', () { - expect(initialPlatform, isInstanceOf()); - }); - - test('getPlatformVersion', () async { - BugsnagFlutterPerformance bugsnagFlutterPerformancePlugin = BugsnagFlutterPerformance(); - MockBugsnagFlutterPerformancePlatform fakePlatform = MockBugsnagFlutterPerformancePlatform(); - BugsnagFlutterPerformancePlatform.instance = fakePlatform; - - expect(await bugsnagFlutterPerformancePlugin.getPlatformVersion(), '42'); - }); -} From e2781a062379ba0c1040f1e2b4ef9cbad76e3a0c Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:14:21 +0200 Subject: [PATCH 03/66] JSON serialisation (#3) * Implemented JSON serialisation for span * Added unit tests --------- Co-authored-by: Robert --- .../lib/src/extensions/date_time.dart | 3 + .../lib/src/extensions/int.dart | 4 + .../lib/src/span.dart | 16 +++ .../test/src/client_test.dart | 46 ++++++ .../test/src/span_test.dart | 131 ++++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 packages/bugsnag_flutter_performance/lib/src/extensions/date_time.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/extensions/int.dart create mode 100644 packages/bugsnag_flutter_performance/test/src/client_test.dart create mode 100644 packages/bugsnag_flutter_performance/test/src/span_test.dart diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/date_time.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/date_time.dart new file mode 100644 index 0000000..2cfd57f --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/date_time.dart @@ -0,0 +1,3 @@ +extension DateTimeNanos on DateTime { + int get nanosecondsSinceEpoch => (microsecondsSinceEpoch * 1000); +} diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/int.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/int.dart new file mode 100644 index 0000000..07007a9 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/int.dart @@ -0,0 +1,4 @@ +extension IntToNanos on int { + DateTime get timeFromNanos => + DateTime.fromMicrosecondsSinceEpoch(this ~/ 1000); +} diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index e4a653f..69561bf 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -1,3 +1,6 @@ +import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; +import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; + abstract class BugsnagPerformanceSpan { void end(); } @@ -16,4 +19,17 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { endTime = DateTime.now(); } + + BugsnagPerformanceSpanImpl.fromJson(Map json) + : startTime = (json['startTimeUnixNano'] as int).timeFromNanos, + name = json['name'] as String, + endTime = json['endTimeUnixNano'] != null + ? (json['endTimeUnixNano'] as int).timeFromNanos + : null; + + dynamic toJson() => { + 'startTimeUnixNano': startTime.nanosecondsSinceEpoch, + 'name': name, + if (endTime != null) 'endTimeUnixNano': endTime!.nanosecondsSinceEpoch, + }; } diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart new file mode 100644 index 0000000..de84c96 --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -0,0 +1,46 @@ +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const apiKey = 'TestApiKey'; + final endpoint = Uri.tryParse('https://bugsnag.com')!; + group('BugsnagPerformanceClient', () { + late BugsnagPerformanceClient client; + + setUp(() { + client = BugsnagPerformanceClient(); + }); + group('start', () { + test('should set configuration with the provided parameters', () { + client.start(apiKey: apiKey, endpoint: endpoint); + + expect(client.configuration!.apiKey, equals(apiKey)); + expect(client.configuration!.endpoint, equals(endpoint)); + }); + }); + + group('startSpan', () { + const name = 'TestSpanName'; + test( + 'should return a running span with the provided name and current time', + () { + final timeBeforeStart = DateTime.now(); + final span = client.startSpan(name) as BugsnagPerformanceSpanImpl; + final timeAfterStart = DateTime.now(); + + expect(span.name, equals(name)); + expect( + span.startTime.nanosecondsSinceEpoch >= + timeBeforeStart.nanosecondsSinceEpoch, + isTrue); + expect( + span.startTime.nanosecondsSinceEpoch <= + timeAfterStart.nanosecondsSinceEpoch, + isTrue); + expect(span.endTime, isNull); + }); + }); + }); +} diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart new file mode 100644 index 0000000..70dbfe5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -0,0 +1,131 @@ +import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; +import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const millisecondsSinceEpoch = 1640979000000; + group('BugsnagPerformanceSpanImpl', () { + test('should have the provided name and provided start time', () { + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true)); + expect( + span.name, + equals('Test name'), + ); + expect( + span.startTime, + equals(DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true)), + ); + expect(span.endTime, isNull); + }); + + group('end', () { + test('should set the current time as end time if the span has not ended', + () { + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true)); + final timeBeforeEnd = DateTime.now(); + span.end(); + final timeAfterEnd = DateTime.now(); + expect( + span.endTime!.nanosecondsSinceEpoch >= + timeBeforeEnd.nanosecondsSinceEpoch, + isTrue); + expect( + span.endTime!.nanosecondsSinceEpoch <= + timeAfterEnd.nanosecondsSinceEpoch, + isTrue); + }); + + test('should not override the end time if the span has ended', () async { + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true)); + span.end(); + final firstEndTime = span.endTime; + await Future.delayed(const Duration(milliseconds: 10)); + span.end(); + expect(span.endTime!.nanosecondsSinceEpoch, + equals(firstEndTime!.nanosecondsSinceEpoch)); + }); + }); + + group('fromJson', () { + test('should decode an ended span', () { + const endTime = millisecondsSinceEpoch + 100; + const name = 'Test name'; + final json = { + 'name': name, + 'startTimeUnixNano': DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true) + .nanosecondsSinceEpoch, + 'endTimeUnixNano': + DateTime.fromMillisecondsSinceEpoch(endTime, isUtc: true) + .nanosecondsSinceEpoch, + }; + final span = BugsnagPerformanceSpanImpl.fromJson(json); + expect(span.name, equals(name)); + expect(span.startTime, + equals((json['startTimeUnixNano'] as int).timeFromNanos)); + expect(span.endTime, + equals((json['endTimeUnixNano'] as int).timeFromNanos)); + }); + + test('should decode a running span', () { + const name = 'Test name'; + final json = { + 'name': name, + 'startTimeUnixNano': DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true) + .nanosecondsSinceEpoch, + }; + final span = BugsnagPerformanceSpanImpl.fromJson(json); + expect(span.name, equals(name)); + expect(span.startTime, + equals((json['startTimeUnixNano'] as int).timeFromNanos)); + expect(span.endTime, isNull); + }); + }); + + group('toJson', () { + test('should encode an ended span', () { + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true)); + span.end(); + final json = span.toJson(); + expect(json['name'], equals(span.name)); + expect(json['startTimeUnixNano'], + equals(span.startTime.nanosecondsSinceEpoch)); + expect(json['endTimeUnixNano'], + equals(span.endTime!.nanosecondsSinceEpoch)); + }); + + test('should encode a running span', () { + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch( + millisecondsSinceEpoch, + isUtc: true)); + final json = span.toJson(); + expect(json['name'], equals(span.name)); + expect(json['startTimeUnixNano'], + equals(span.startTime.nanosecondsSinceEpoch)); + expect(json['endTimeUnixNano'], isNull); + }); + }); + }); +} From ad622a93752b4a221c3bc26ce729949472679047 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:43:41 +0200 Subject: [PATCH 04/66] Span batching and sending (#5) * [PLAT-10789] Implement span batching and sending for Flutter Performance * Added default endpoint and a small fix * Added Span Sampling hardcoded value --------- Co-authored-by: Robert --- .../lib/bugsnag_flutter_performance.dart | 1 - .../lib/src/client.dart | 54 ++++- .../lib/src/configuration.dart | 1 + .../lib/src/span.dart | 77 ++++++- .../lib/src/uploader/client_request.dart | 34 +++ .../lib/src/uploader/client_response.dart | 15 ++ .../lib/src/uploader/model/otlp_package.dart | 11 + .../lib/src/uploader/package_builder.dart | 63 ++++++ .../lib/src/uploader/span_batch.dart | 68 ++++++ .../lib/src/uploader/uploader.dart | 34 +++ .../lib/src/uploader/uploader_client.dart | 19 ++ .../lib/src/util/random.dart | 19 ++ .../test/src/span_test.dart | 71 +++++- .../test/src/uploader/span_batch_test.dart | 209 ++++++++++++++++++ 14 files changed, 660 insertions(+), 16 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/client_request.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/model/otlp_package.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/uploader_client.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/util/random.dart create mode 100644 packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 1e181d2..ab70915 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -1,5 +1,4 @@ library bugsnag_flutter_performance; export 'src/client.dart'; -export 'src/configuration.dart'; export 'src/span.dart' show BugsnagPerformanceSpan; diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 7168533..e262be3 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -1,6 +1,15 @@ +import 'dart:io'; + +import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; + import 'configuration.dart'; import 'span.dart'; +const _defaultEndpoint = 'https://otlp.bugsnag.com/v1/traces'; + abstract class BugsnagPerformance { Future start({String? apiKey, Uri? endpoint}); BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}); @@ -8,17 +17,56 @@ abstract class BugsnagPerformance { class BugsnagPerformanceClient implements BugsnagPerformance { BugsnagPerformanceConfiguration? configuration; + Uploader? _uploader; + SpanBatch? _currentBatch; + final PackageBuilder _packageBuilder = PackageBuilderImpl(); @override Future start({String? apiKey, Uri? endpoint}) async { configuration = BugsnagPerformanceConfiguration( apiKey: apiKey, - endpoint: endpoint, + endpoint: endpoint ?? Uri.parse(_defaultEndpoint), ); + _setup(); } @override BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}) { - return BugsnagPerformanceSpanImpl( - name: name, startTime: startTime ?? DateTime.now()); + final span = BugsnagPerformanceSpanImpl( + name: name, + startTime: startTime ?? DateTime.now(), + onEnded: (endedSpan) { + _currentBatch?.add(endedSpan); + }, + ); + if (configuration != null) { + _currentBatch ??= SpanBatchImpl(); + _currentBatch?.configure(configuration!); + _currentBatch?.onBatchFull = _sendBatch; + _currentBatch?.add(span); + } + return span; + } + + void _setup() { + if (configuration?.endpoint != null && configuration?.apiKey != null) { + _uploader = UploaderImpl( + apiKey: configuration!.apiKey!, + url: configuration!.endpoint!, + client: UploaderClientImpl(httpClient: HttpClient()), + ); + } + } + + void _sendBatch(SpanBatch batch) { + final spans = batch.drain(); + if (spans.isEmpty) { + return; + } + final package = _packageBuilder.build(spans); + _uploader?.upload(package: package); + } + + void setBatchSize(int batchSize) { + configuration?.autoTriggerExportOnBatchSize = batchSize; } } diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index 8ed1a6d..f2098ac 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -2,4 +2,5 @@ class BugsnagPerformanceConfiguration { BugsnagPerformanceConfiguration({this.apiKey, this.endpoint}); String? apiKey; Uri? endpoint; + int autoTriggerExportOnBatchSize = 100; } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 69561bf..78c590c 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -1,15 +1,41 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; +import 'package:bugsnag_flutter_performance/src/util/random.dart'; + +typedef TraceId = BigInt; +typedef SpanId = BigInt; abstract class BugsnagPerformanceSpan { + TraceId get traceId; + SpanId get spanId; + SpanId? parentSpanId; void end(); + dynamic toJson(); } class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { - BugsnagPerformanceSpanImpl({required this.name, required this.startTime}); - final DateTime startTime; + BugsnagPerformanceSpanImpl({ + required this.name, + required this.startTime, + void Function(BugsnagPerformanceSpan)? onEnded, + TraceId? traceId, + SpanId? spanId, + this.parentSpanId, + }) { + this.traceId = traceId ?? randomTraceId(); + this.spanId = spanId ?? randomSpanId(); + this.onEnded = onEnded ?? _onEnded; + } final String name; + @override + late final TraceId traceId; + @override + late final SpanId spanId; + @override + SpanId? parentSpanId; + final DateTime startTime; DateTime? endTime; + late final void Function(BugsnagPerformanceSpan) onEnded; @override void end() { @@ -20,16 +46,59 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { endTime = DateTime.now(); } - BugsnagPerformanceSpanImpl.fromJson(Map json) + BugsnagPerformanceSpanImpl.fromJson(Map json, + [void Function(BugsnagPerformanceSpan)? onEnded]) : startTime = (json['startTimeUnixNano'] as int).timeFromNanos, name = json['name'] as String, endTime = json['endTimeUnixNano'] != null ? (json['endTimeUnixNano'] as int).timeFromNanos - : null; + : null, + traceId = _decodeTraceId(json['traceId'] as String?) ?? randomTraceId(), + spanId = _decodeSpanId(json['spanId'] as String?) ?? randomSpanId(), + parentSpanId = _decodeSpanId(json['parentSpanId'] as String?), + onEnded = onEnded ?? _onEnded; + @override dynamic toJson() => { 'startTimeUnixNano': startTime.nanosecondsSinceEpoch, 'name': name, if (endTime != null) 'endTimeUnixNano': endTime!.nanosecondsSinceEpoch, + 'traceId': _encodeTraceId(traceId), + 'spanId': _encodeSpanId(spanId), + if (parentSpanId != null) + 'parentSpanId': _encodeSpanId(parentSpanId ?? BigInt.zero), }; + + @override + bool operator ==(dynamic other) => + other is BugsnagPerformanceSpan && + other.spanId == spanId && + other.traceId == traceId; + + @override + int get hashCode => toJson().hashCode; } + +String _encodeSpanId(SpanId spanId) { + return spanId.toRadixString(16); +} + +String _encodeTraceId(TraceId traceId) { + return traceId.toRadixString(16); +} + +TraceId? _decodeTraceId(String? traceIdString) { + if (traceIdString == null) { + return null; + } + return BigInt.tryParse(traceIdString, radix: 16); +} + +SpanId? _decodeSpanId(String? spanIdString) { + if (spanIdString == null) { + return null; + } + return BigInt.tryParse(spanIdString, radix: 16); +} + +void _onEnded(BugsnagPerformanceSpan span) {} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/client_request.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/client_request.dart new file mode 100644 index 0000000..9811cd5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/client_request.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:bugsnag_flutter_performance/src/uploader/client_response.dart'; +import 'package:flutter/foundation.dart'; + +abstract class ClientRequest { + void setHeaders(Map headers); + void setBody(Uint8List body); + Future send(); +} + +class ClientRequestImpl implements ClientRequest { + final HttpClientRequest request; + ClientRequestImpl({ + required this.request, + }); + + @override + void setHeaders(Map headers) { + headers.forEach((key, value) { + request.headers.add(key, value); + }); + } + + @override + void setBody(List body) { + request.add(body); + } + + @override + Future send() async { + return ClientResponseImpl(response: await request.close()); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart new file mode 100644 index 0000000..4a11931 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +abstract class ClientResponse { + int get statusCode; +} + +class ClientResponseImpl implements ClientResponse { + HttpClientResponse response; + ClientResponseImpl({ + required this.response, + }); + + @override + int get statusCode => response.statusCode; +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/model/otlp_package.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/model/otlp_package.dart new file mode 100644 index 0000000..52a2d81 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/model/otlp_package.dart @@ -0,0 +1,11 @@ +import 'package:flutter/foundation.dart'; + +class OtlpPackage { + final Map headers; + final Uint8List payload; + + OtlpPackage({ + required this.headers, + required this.payload, + }); +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart new file mode 100644 index 0000000..c659482 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; + +const int _minSizeForGzip = 128; + +abstract class PackageBuilder { + OtlpPackage build( + List spans, + ); +} + +class PackageBuilderImpl implements PackageBuilder { + @override + OtlpPackage build(List spans) { + var payload = _buildPayload(spans: spans); + var isZipped = false; + final uncompressedDataLength = payload.length; + if (payload.length >= _minSizeForGzip) { + payload = GZipCodec().encode(payload); + isZipped = true; + } + final headers = _buildHeaders( + payload: payload, + uncompressedDataLength: uncompressedDataLength, + isZipped: isZipped, + ); + return OtlpPackage( + headers: headers, + payload: Uint8List.fromList(payload), + ); + } + + List _buildPayload({ + required List spans, + }) { + final jsonList = spans.map((span) => span.toJson()).toList(); + final json = jsonEncode(jsonList); + return utf8.encode(json); + } + + Map _buildHeaders({ + required List payload, + required int uncompressedDataLength, + required bool isZipped, + }) { + return { + 'Content-Type': 'application/json', + 'Bugsnag-Integrity': _integrityDigestForData(payload: payload), + 'Bugsnag-Uncompressed-Content-Length': uncompressedDataLength.toString(), + if (isZipped) 'Content-Encoding': 'gzip' + }; + } + + String _integrityDigestForData({ + required List payload, + }) { + return 'sha1 ${payload.map((e) => e.toRadixString(16)).join()}'; + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart new file mode 100644 index 0000000..36a4d96 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart @@ -0,0 +1,68 @@ +import 'package:bugsnag_flutter_performance/src/configuration.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; + +abstract class SpanBatch { + void configure(BugsnagPerformanceConfiguration configuration); + void add(BugsnagPerformanceSpan span); + void remove(TraceId traceId, SpanId spanId); + void allowDrain(); + List drain({bool? force}); + void Function(SpanBatch batch) onBatchFull = (_) {}; +} + +class SpanBatchImpl implements SpanBatch { + List _spans = []; + int _autoTriggerExportOnBatchSize = 0; + bool _drainIsAllowed = false; + + @override + void Function(SpanBatch batch) onBatchFull = (_) {}; + + @override + void configure(BugsnagPerformanceConfiguration configuration) { + _autoTriggerExportOnBatchSize = configuration.autoTriggerExportOnBatchSize; + } + + @override + void add(BugsnagPerformanceSpan span) { + _spans.add(span); + if (_isFull) { + _drainIsAllowed = true; + onBatchFull(this); + } + } + + @override + void allowDrain() { + _drainIsAllowed = true; + } + + @override + List drain({bool? force}) { + final isForced = force ?? false; + if (!(_drainIsAllowed || isForced)) { + return []; + } + final spans = _spans.toList(); + _spans.clear(); + return spans; + } + + @override + void remove(TraceId traceId, SpanId spanId) { + final spans = _spans + .where((span) => !(span.traceId == traceId && span.spanId == spanId)) + .toList(); + if (_spans.length != spans.length) { + _spans = spans; + _spans + .where( + (span) => span.traceId == traceId && span.parentSpanId == spanId) + .forEach((span) { + span.parentSpanId = null; + }); + } + } + + bool get _isFull => _spans.length >= _autoTriggerExportOnBatchSize; +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart new file mode 100644 index 0000000..f4162b7 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -0,0 +1,34 @@ +import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; + +abstract class Uploader { + Future upload({required OtlpPackage package}); +} + +class UploaderImpl implements Uploader { + final String apiKey; + final Uri url; + final UploaderClient client; + UploaderImpl({ + required this.apiKey, + required this.url, + required this.client, + }); + + @override + Future upload({ + required OtlpPackage package, + }) async { + var headers = { + 'Bugsnag-Api-Key': apiKey, + 'Bugsnag-Sent-At': DateTime.now().toUtc().toIso8601String(), + 'Bugsnag-Span-Sampling': '1:1' + }; + headers.addAll(package.headers); + final request = await client.post(url: url); + request.setHeaders(headers); + request.setBody(package.payload); + final response = await request.send(); + return response.statusCode / 100 == 2; + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader_client.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader_client.dart new file mode 100644 index 0000000..19c86f5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader_client.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:bugsnag_flutter_performance/src/uploader/client_request.dart'; + +abstract class UploaderClient { + Future post({required Uri url}); +} + +class UploaderClientImpl implements UploaderClient { + final HttpClient httpClient; + UploaderClientImpl({ + required this.httpClient, + }); + + @override + Future post({required Uri url}) async { + return ClientRequestImpl(request: await httpClient.postUrl(url)); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/util/random.dart b/packages/bugsnag_flutter_performance/lib/src/util/random.dart new file mode 100644 index 0000000..87acd4c --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/util/random.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +BigInt randomSpanId() { + return randomValue(8); +} + +BigInt randomTraceId() { + return randomValue(16); +} + +BigInt randomValue(int length) { + BigInt result = BigInt.zero; + final random = Random.secure(); + for (var i = 0; i < length; i++) { + int byte = random.nextInt(256); + result = (result << 8) | BigInt.from(byte & 0xff); + } + return result; +} diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 70dbfe5..d21cd09 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -1,16 +1,22 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/util/random.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const millisecondsSinceEpoch = 1640979000000; group('BugsnagPerformanceSpanImpl', () { - test('should have the provided name and provided start time', () { + test('should have the provided name, start time, traceId and spanId', () { + final traceId = randomTraceId(); + final spanId = randomSpanId(); final span = BugsnagPerformanceSpanImpl( - name: 'Test name', - startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, - isUtc: true)); + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true), + traceId: traceId, + spanId: spanId, + ); expect( span.name, equals('Test name'), @@ -20,9 +26,41 @@ void main() { equals(DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, isUtc: true)), ); + expect(span.traceId, equals(traceId)); + expect(span.spanId, equals(spanId)); + expect(span.parentSpanId, isNull); expect(span.endTime, isNull); }); + test('should have random traceId and spanId if it is not provided', () { + final span1 = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true), + ); + final span2 = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true), + ); + expect(span1.spanId != span2.spanId, isTrue); + expect(span1.traceId != span2.traceId, isTrue); + }); + + test('should have the provided parentId', () { + final parentSpanId = randomSpanId(); + final traceId = randomTraceId(); + final span = BugsnagPerformanceSpanImpl( + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true), + traceId: traceId, + parentSpanId: parentSpanId, + ); + expect(span.traceId, equals(traceId)); + expect(span.parentSpanId, equals(parentSpanId)); + }); + group('end', () { test('should set the current time as end time if the span has not ended', () { @@ -72,6 +110,9 @@ void main() { 'endTimeUnixNano': DateTime.fromMillisecondsSinceEpoch(endTime, isUtc: true) .nanosecondsSinceEpoch, + 'traceId': 'ffa74cc50baa432515e9b28fc4abf2cb', + 'spanId': 'fa0e2d25f149f215', + 'parentSpanId': '6293f00f47da54de', }; final span = BugsnagPerformanceSpanImpl.fromJson(json); expect(span.name, equals(name)); @@ -79,6 +120,14 @@ void main() { equals((json['startTimeUnixNano'] as int).timeFromNanos)); expect(span.endTime, equals((json['endTimeUnixNano'] as int).timeFromNanos)); + expect(span.traceId, + equals(BigInt.tryParse(json['traceId'] as String, radix: 16)!)); + expect(span.spanId, + equals(BigInt.tryParse(json['spanId'] as String, radix: 16)!)); + expect( + span.parentSpanId, + equals( + BigInt.tryParse(json['parentSpanId'] as String, radix: 16)!)); }); test('should decode a running span', () { @@ -101,10 +150,12 @@ void main() { group('toJson', () { test('should encode an ended span', () { final span = BugsnagPerformanceSpanImpl( - name: 'Test name', - startTime: DateTime.fromMillisecondsSinceEpoch( - millisecondsSinceEpoch, - isUtc: true)); + name: 'Test name', + startTime: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, + isUtc: true), + traceId: randomTraceId(), + parentSpanId: randomSpanId(), + ); span.end(); final json = span.toJson(); expect(json['name'], equals(span.name)); @@ -112,6 +163,10 @@ void main() { equals(span.startTime.nanosecondsSinceEpoch)); expect(json['endTimeUnixNano'], equals(span.endTime!.nanosecondsSinceEpoch)); + expect(json['traceId'], equals(span.traceId.toRadixString(16))); + expect(json['spanId'], equals(span.spanId.toRadixString(16))); + expect( + json['parentSpanId'], equals(span.parentSpanId!.toRadixString(16))); }); test('should encode a running span', () { diff --git a/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart b/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart new file mode 100644 index 0000000..ade093d --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart @@ -0,0 +1,209 @@ +import 'package:bugsnag_flutter_performance/src/configuration.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('SpanBatch', () { + late SpanBatch batch; + + setUp(() { + batch = SpanBatchImpl(); + }); + + test('should trigger batch full every time if not configured', () { + int batchFullTriggeredCount = 0; + batch.onBatchFull = (_) => batchFullTriggeredCount++; + + final span1 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span2 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + batch.add(span1); + expect(batchFullTriggeredCount, equals(1)); + batch.add(span2); + expect(batchFullTriggeredCount, equals(2)); + + expect(batch.drain(), equals([span1, span2])); + }); + + group('when configured', () { + const batchSize = 4; + + setUp(() { + batch.configure( + BugsnagPerformanceConfiguration() + ..autoTriggerExportOnBatchSize = batchSize, + ); + }); + + group('add', () { + test( + 'should trigger batch full after it reaches export on batch size value', + () { + int batchFullTriggeredCount = 0; + batch.onBatchFull = (_) => batchFullTriggeredCount++; + + batch.add( + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now())); + expect(batchFullTriggeredCount, equals(0)); + batch.add( + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now())); + expect(batchFullTriggeredCount, equals(0)); + batch.add( + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now())); + expect(batchFullTriggeredCount, equals(0)); + batch.add( + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now())); + expect(batchFullTriggeredCount, equals(1)); + }); + }); + + group('remove', () { + test('should remove the span from the batch', () { + final unrelatedSpan1 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final spanToRemove = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final unrelatedSpan2 = BugsnagPerformanceSpanImpl( + name: '', + startTime: DateTime.now(), + parentSpanId: unrelatedSpan1.spanId, + ); + final unrelatedSpan3 = BugsnagPerformanceSpanImpl( + name: '', + startTime: DateTime.now(), + parentSpanId: unrelatedSpan2.spanId, + ); + batch.add(unrelatedSpan1); + batch.add(spanToRemove); + batch.add(unrelatedSpan2); + batch.add(unrelatedSpan3); + + batch.remove(spanToRemove.traceId, spanToRemove.spanId); + batch.allowDrain(); + expect( + batch.drain(), + equals([unrelatedSpan1, unrelatedSpan2, unrelatedSpan3]), + ); + }); + + test('should change parentId of child spans', () { + final unrelatedSpan1 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final spanToRemove = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final childSpan1 = BugsnagPerformanceSpanImpl( + name: '', + startTime: DateTime.now(), + traceId: spanToRemove.traceId, + parentSpanId: spanToRemove.spanId, + ); + final spanFromAnotherTrace = BugsnagPerformanceSpanImpl( + name: '', + startTime: DateTime.now(), + parentSpanId: spanToRemove.spanId, + ); + final childSpan2 = BugsnagPerformanceSpanImpl( + name: '', + startTime: DateTime.now(), + traceId: spanToRemove.traceId, + parentSpanId: spanToRemove.spanId, + ); + batch.add(unrelatedSpan1); + batch.add(spanToRemove); + batch.add(childSpan1); + batch.add(spanFromAnotherTrace); + batch.add(childSpan2); + + batch.remove(spanToRemove.traceId, spanToRemove.spanId); + batch.allowDrain(); + final spans = batch.drain(); + expect(spans.length, equals(4)); + expect(spans[0].spanId, equals(unrelatedSpan1.spanId)); + expect(spans[0].parentSpanId, isNull); + expect(spans[1].spanId, equals(childSpan1.spanId)); + expect(spans[1].parentSpanId, isNull); + expect(spans[2].spanId, equals(spanFromAnotherTrace.spanId)); + expect(spans[2].parentSpanId, equals(spanToRemove.spanId)); + expect(spans[3].spanId, equals(childSpan2.spanId)); + expect(spans[3].parentSpanId, isNull); + }); + }); + + group('drain', () { + test( + 'should return empty list if the batch is not full and it is not forced', + () { + batch.add( + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now())); + expect(batch.drain().length, equals(0)); + }); + + test('should return all spans if the batch is full', () { + final span1 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span2 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span3 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span4 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + batch.add(span1); + batch.add(span2); + batch.add(span3); + batch.add(span4); + expect( + batch.drain(), + equals([span1, span2, span3, span4]), + ); + }); + + test('should clear the spans list', () { + final span1 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span2 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span3 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + final span4 = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + batch.add(span1); + batch.add(span2); + batch.add(span3); + batch.add(span4); + batch.drain(); + expect( + batch.drain(), + equals([]), + ); + }); + + test( + 'should return the spans if the batch is not full and it is forced', + () { + final span = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + batch.add(span); + expect( + batch.drain(force: true), + equals([span]), + ); + }); + + test( + 'should return the spans if the batch is not full and allowDrain was called', + () { + final span = + BugsnagPerformanceSpanImpl(name: '', startTime: DateTime.now()); + batch.add(span); + batch.allowDrain(); + expect( + batch.drain(), + equals([span]), + ); + }); + }); + }); + }); +} From 34d6dd76ca231090e139c3d55fd7cd319e14e1be Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 25 Oct 2023 16:31:47 +0200 Subject: [PATCH 05/66] PLAT-10784 initial CI and E2E setup (#4) * initial commit to see if anything works * reset native modules * ios export options * proper bundle id * ios team fix * update * bundle fix * generate fixture * export fix * remove export copy * typo * logging * env var * xcodebuild * revert * sed magic * switch to app * auto code sign * build setting * typo * brute force * remove key * pipe * pipe * upload * ttt * ttt * correct artiefact * java version * update build example app step to use correct example names * simple artifact upload * debug data * clean * pipe * debug * remove unnecessary test * copy over dart code * generate fixes * try running 1 feature * pipe fix * http methods * add android internet permission * remove clear persistant data * get conifg * address formatting * logging * hardcoded url for debugging * disable gzip for debugging * using mazerunner logs * re enable gzip * typo * more test * review changes * Update .gitignore Co-authored-by: Tom Longridge --------- Co-authored-by: Josh Edney Co-authored-by: Tom Longridge --- .buildkite/pipeline.yml | 67 +++- .gitignore | 3 + Makefile | 2 +- .../fixture_resources/exportOptions.plist | 8 + features/fixture_resources/lib/channels.dart | 24 ++ features/fixture_resources/lib/main.dart | 318 ++++++++++++++++++ features/fixture_resources/lib/packages.dart | 18 + .../lib/scenarios/manual_span_scenario.dart | 19 ++ .../lib/scenarios/scenario.dart | 26 ++ .../lib/scenarios/scenarios.dart | 13 + features/manual_span.feature | 6 + features/scripts/build_android_app.sh | 11 + features/scripts/build_ios_app.sh | 12 + features/scripts/generate_fixture.sh | 56 +++ features/scripts/save_fixture_changes.sh | 13 + features/steps/flutter_steps.rb | 55 +++ features/support/env.rb | 13 + 17 files changed, 653 insertions(+), 11 deletions(-) create mode 100644 features/fixture_resources/exportOptions.plist create mode 100644 features/fixture_resources/lib/channels.dart create mode 100644 features/fixture_resources/lib/main.dart create mode 100644 features/fixture_resources/lib/packages.dart create mode 100644 features/fixture_resources/lib/scenarios/manual_span_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/scenarios.dart create mode 100644 features/manual_span.feature create mode 100755 features/scripts/build_android_app.sh create mode 100755 features/scripts/build_ios_app.sh create mode 100755 features/scripts/generate_fixture.sh create mode 100755 features/scripts/save_fixture_changes.sh create mode 100644 features/steps/flutter_steps.rb create mode 100644 features/support/env.rb diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 207029f..d032a26 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -17,16 +17,63 @@ steps: commands: - make lint - - label: Build Example App 3.10.0 - timeout_in_minutes: 10 + # + # iOS + # + - label: Build iOS Test Fixture + key: "ios-fixture-3-10-0" + timeout_in_minutes: 20 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + commands: + - pod repo update trunk + - bundle install + - features/scripts/generate_fixture.sh + - features/scripts/build_ios_app.sh + plugins: + artifacts#v1.5.0: + upload: + - "features/fixtures/mazerunner/build/ios/ipa/mazerunner.ipa" + # + # Android + # + - label: Build Android Test Fixture + key: "android-fixture-3-10-0" + timeout_in_minutes: 20 env: FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + JAVA_VERSION: "11" commands: - - pod repo update - - make examples/flutter - # Verify that App.framework's UUID (and therefore our `codeIdentifier`) changes when Dart code is touched - - dwarfdump --arch=arm64 --uuid examples/flutter/build/ios/iphoneos/Runner.app/Frameworks/App.framework/App | tee uuid_before - - sed -i '' -e 's/add_your_api_key_here/my_api_key/' examples/flutter/lib/main.dart - - make examples/flutter - - dwarfdump --arch=arm64 --uuid examples/flutter/build/ios/iphoneos/Runner.app/Frameworks/App.framework/App | tee uuid_after - - test "$(cat uuid_before)" != "$(cat uuid_after)" + - bundle install + - features/scripts/generate_fixture.sh + - features/scripts/build_android_app.sh + plugins: + artifacts#v1.5.0: + upload: + - "features/fixtures/mazerunner/build/app/outputs/flutter-apk/app-release.apk" + + - label: ':bitbar: Android 12 end-to-end tests 3.10.0' + depends_on: "android-fixture-3-10-0" + timeout_in_minutes: 20 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/mazerunner/build/app/outputs/flutter-apk/app-release.apk" + upload: "maze_output/failed/**/*" + docker-compose#v4.7.0: + pull: maze-runner + run: maze-runner + service-ports: true + command: + - "--app=features/fixtures/mazerunner/build/app/outputs/flutter-apk/app-release.apk" + - "--farm=bb" + - "--device=ANDROID_10|ANDROID_11|ANDROID_12|ANDROID_13" + - "--no-tunnel" + - "--aws-public-ip" + - "--fail-fast" + concurrency: 25 + concurrency_group: 'bitbar' + concurrency_method: eager \ No newline at end of file diff --git a/.gitignore b/.gitignore index 981f525..eb8de57 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ doc/api/ Package.resolved Podfile.lock maze_output/ + +# ignored as fixtures are generated +features/fixtures diff --git a/Makefile b/Makefile index 28d5cc8..a6df58a 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ aar: examples/bugsnag_performance_example: cd $@ && $(FLUTTER_BIN) pub get - cd $@ && $(FLUTTER_BIN) build apk --suppress-analytics --no-tree-shake-icons + cd $@ && $(FLUTTER_BIN) build apk --suppress-analytics cd $@ && $(FLUTTER_BIN) build ios --no-codesign --suppress-analytics --no-tree-shake-icons test: diff --git a/features/fixture_resources/exportOptions.plist b/features/fixture_resources/exportOptions.plist new file mode 100644 index 0000000..1c702b5 --- /dev/null +++ b/features/fixture_resources/exportOptions.plist @@ -0,0 +1,8 @@ + + + + + teamID + 7W9PZ27Y5F + + diff --git a/features/fixture_resources/lib/channels.dart b/features/fixture_resources/lib/channels.dart new file mode 100644 index 0000000..ac4ef2a --- /dev/null +++ b/features/fixture_resources/lib/channels.dart @@ -0,0 +1,24 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/services.dart'; + +class MazeRunnerChannels { + static const platform = MethodChannel('com.bugsnag.mazeRunner/platform'); + + static Future getCommand(String commandUrl) async { + return await platform.invokeMethod("getCommand", { + "commandUrl": commandUrl, + }).then((value) => value ?? ""); + } + + static Future clearPersistentData() { + return platform.invokeMethod('clearPersistentData'); + } + + static Future runScenario(String scenarioName, + {Map? arguments}) async => + platform.invokeMethod('runScenario', { + 'scenarioName': scenarioName, + ...?arguments, + }); + +} diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart new file mode 100644 index 0000000..3f63aca --- /dev/null +++ b/features/fixture_resources/lib/main.dart @@ -0,0 +1,318 @@ +// ignore_for_file: avoid_print + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:mazerunner/channels.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'scenarios/scenario.dart'; +import 'scenarios/scenarios.dart'; +import 'package:http/http.dart' as http; + +void log(String message) { + print('[MazeRunner] $message'); +} + +void main() { + runApp(const MazeRunnerFlutterApp()); +} + +extension StringGet on Map { + String? string(K key) { + final value = this[key]; + return value is String ? value : null; + } +} +class FixtureConfig +{ + static Uri MAZE_HOST = Uri.parse(""); +} +/// Represents a MazeRunner command +class Command { + final String action; + final String scenarioName; + final String extraConfig; + + const Command({ + required this.action, + required this.scenarioName, + required this.extraConfig, + }); + + + + factory Command.fromJsonString(String jsonString) { + final map = json.decode(jsonString) as Map; + if (map['action'] == null) { + throw Exception('MazeRunner commands must have an action'); + } + return Command( + action: map.string('action')!, + scenarioName: map.string('scenario_name') ?? '', + extraConfig: map.string('extra_config') ?? '', + ); + } +} + +class MazeRunnerFlutterApp extends StatelessWidget { + const MazeRunnerFlutterApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Bugsnag Test', + theme: ThemeData( + primaryColor: const Color.fromARGB(255, 73, 73, 227), + ), + home: FutureBuilder( + future: Future(() async { + for (var i = 0; i < 30; i++) { + try { + final Directory directory = await appFilesDirectory(); + final File file = File('${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); + final text = await file.readAsString(); + print("fixture_config.json found with contents: $text"); + Map json = jsonDecode(text); + if (json.containsKey('maze_address')) { + print("fixture_config.json found with contents: $text"); + FixtureConfig.MAZE_HOST = Uri.parse("http://" + json['maze_address']); + return json['maze_address']; + } + } catch (e) { + print("Couldn't read fixture_config.json: $e"); + } + await Future.delayed(const Duration(seconds: 1)); + } + print("fixture_config.json not read within 30s, defaulting to BrowserStack address"); + FixtureConfig.MAZE_HOST = Uri.parse('bs-local.com:9339'); + return 'bs-local.com:9339'; + }), + builder: (_, mazerunnerUrl) { + if (mazerunnerUrl.data != null) { + return MazeRunnerHomePage(mazerunnerUrl: mazerunnerUrl.data!,); + } else { + return Container(color: Colors.white, child: const Center(child: CircularProgressIndicator())); + } + }), + ); + } + + Future appFilesDirectory() async { + if (Platform.isAndroid) { + return await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory(); + } + return await getApplicationDocumentsDirectory(); + } + +} + +class MazeRunnerHomePage extends StatefulWidget { + final String mazerunnerUrl; + const MazeRunnerHomePage({Key? key, required this.mazerunnerUrl,}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + late TextEditingController _scenarioNameController; + late TextEditingController _extraConfigController; + late TextEditingController _commandEndpointController; + late TextEditingController _notifyEndpointController; + late TextEditingController _sessionEndpointController; + + @override + void initState() { + super.initState(); + _scenarioNameController = TextEditingController(); + _extraConfigController = TextEditingController(); + _commandEndpointController = TextEditingController( + text: 'http://${widget.mazerunnerUrl}/command', + ); + _notifyEndpointController = TextEditingController( + text: 'http://${widget.mazerunnerUrl}/notify', + ); + _sessionEndpointController = TextEditingController( + text: 'http://${widget.mazerunnerUrl}/sessions', + ); + _onRunCommand(context, retry: true); + } + + @override + void dispose() { + _scenarioNameController.dispose(); + _extraConfigController.dispose(); + _commandEndpointController.dispose(); + _notifyEndpointController.dispose(); + _sessionEndpointController.dispose(); + + super.dispose(); + } + + /// Fetches the next command + void _onRunCommand(BuildContext context, {bool retry = false}) async { + log('Fetching the next command'); + + final commandUrl = _commandEndpointController.value.text; + + final response = await http + .get(Uri.parse(commandUrl)); + + if (response.statusCode == 200) { + // If the server did return a 200 OK response, + // then parse the JSON. + log('The body is:${response.body}'); + + if (response.body.isEmpty) { + log('Empty command, retrying...'); + if (retry) { + Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + } + return; + } + final command = Command.fromJsonString(response.body); + _scenarioNameController.text = command.scenarioName; + _extraConfigController.text = command.extraConfig; + + + switch (command.action) { + case 'run_scenario': + _onRunScenario(context); + break; + } + } + else + { + if (retry) { + Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + } + } + + } + + /// Starts Bugsnag + Future _onStartBugsnag() async { + log('Starting Bugsnag'); + // await BugsnagPerformance.start(); + } + + /// Runs a scenario, starting bugsnag first + void _onRunScenario(BuildContext context) async { + final scenario = _initScenario(context); + if (scenario == null) { + return; + } + //TODO: Clear persistent data + // await scenario.clearPersistentData(); + + scenario.extraConfig = _extraConfigController.value.text; + + Widget? scenarioWidget = scenario.createWidget(); + if (scenarioWidget != null) { + log('Mounting Scenario Widget'); + final route = MaterialPageRoute(builder: (context) => scenarioWidget); + Navigator.push(context, route); + await route.didPush(); + } + + log('Running scenario'); + await scenario.run(); + } + + /// Initializes a scenario + Scenario? _initScenario(BuildContext context) { + final name = _scenarioNameController.value.text; + log('Initializing scenario: $name'); + final scenarioIndex = + scenarios.indexWhere((element) => element.name == name); + + if (scenarioIndex == -1) { + log('Cannot find Scenario $name. Has it been added to scenarios.dart?'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Cannot find Scenario $name. Has it been added to scenarios.dart?', + ), + ), + ); + + return null; + } + + return scenarios[scenarioIndex].init(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: 400.0, + width: double.infinity, + child: TextButton( + child: const Text("Run Command"), + onPressed: () => _onRunCommand(context), + key: const Key("runCommand"), + )), + TextField( + controller: _scenarioNameController, + key: const Key("scenarioName"), + decoration: const InputDecoration( + label: Text("Scenario Name"), + ), + ), + TextField( + controller: _extraConfigController, + key: const Key("extraConfig"), + decoration: const InputDecoration( + label: Text("Extra Config"), + ), + ), + TextField( + controller: _commandEndpointController, + key: const Key("commandEndpoint"), + decoration: const InputDecoration( + label: Text("Command Endpoint"), + ), + ), + TextField( + controller: _notifyEndpointController, + key: const Key("notifyEndpoint"), + decoration: const InputDecoration( + label: Text("Notify Endpoint"), + ), + ), + TextField( + controller: _sessionEndpointController, + key: const Key("sessionEndpoint"), + decoration: const InputDecoration( + label: Text("Session Endpoint"), + ), + ), + TextButton( + child: const Text("Start Bugsnag"), + onPressed: _onStartBugsnag, + key: const Key("startBugsnag"), + ), + TextButton( + child: const Text("Run Scenario"), + onPressed: () => _onRunScenario(context), + key: const Key("startScenario"), + ), + ], + ), + ), + ), + ); + } +} diff --git a/features/fixture_resources/lib/packages.dart b/features/fixture_resources/lib/packages.dart new file mode 100644 index 0000000..13c8f6a --- /dev/null +++ b/features/fixture_resources/lib/packages.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; + +Future> listPackages() async { + var packages = {}; + // The license registry has a list of licenses entries (MIT, Apache...). + // Each license entry has a list of packages which licensed under this particular license. + // Libraries can be dual licensed. + // + // We don't care about those license issues, we just want each package name once. + // Therefore we add each name to a set to make sure we only add it once. + await LicenseRegistry.licenses.forEach( + (entry) => packages.addAll( + entry.packages.toList(), + ), + ); + + return List.from(packages); +} diff --git a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart new file mode 100644 index 0000000..73c88c3 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart @@ -0,0 +1,19 @@ + +import 'dart:convert'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; +import 'package:http/http.dart' as http; + +class ManualSpan extends Scenario { + @override + Future run() async { + log("running manual span scenario"); + http.post(Uri.parse(FixtureConfig.MAZE_HOST.toString() + "/logs"), body: jsonEncode({ + "message": "manual span scenario" + })); + } +} + diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart new file mode 100644 index 0000000..831cb72 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -0,0 +1,26 @@ +// ignore_for_file: avoid_print +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/widgets.dart'; + +import '../channels.dart'; + +abstract class Scenario { + String? extraConfig; + + Future clearPersistentData() async { + print('[MazeRunner] Clearing Persistent Data...'); + await MazeRunnerChannels.clearPersistentData(); + } + + // Future startBugsnag() => null; + + Widget? createWidget() => null; + + Future run(); +} + +void expect(dynamic actual, dynamic expected) { + if (actual != expected) { + throw AssertionError('Expected \'$expected\' but got \'$actual\''); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart new file mode 100644 index 0000000..24e7a35 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -0,0 +1,13 @@ +import 'manual_span_scenario.dart'; +import 'scenario.dart'; + +class ScenarioInfo { + const ScenarioInfo(this.name, this.init); + final String name; + final Scenario Function() init; +} + +// Flutter obfuscation *requires* that we specify the name as a raw String in order to match the runtime class +final List> scenarios = [ + ScenarioInfo('ManualSpan', () => ManualSpan()) +]; diff --git a/features/manual_span.feature b/features/manual_span.feature new file mode 100644 index 0000000..d47155b --- /dev/null +++ b/features/manual_span.feature @@ -0,0 +1,6 @@ +Feature: Manual Spans + + Scenario: Manual Span + When I run "ManualSpan" + And I wait to receive a log + And the log payload field "message" equals "manual span scenario" \ No newline at end of file diff --git a/features/scripts/build_android_app.sh b/features/scripts/build_android_app.sh new file mode 100755 index 0000000..2954931 --- /dev/null +++ b/features/scripts/build_android_app.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -o errexit + +if [ -z "$FLUTTER_BIN" ]; then + FLUTTER_BIN="flutter" +fi + +echo "Flutter Bin: $FLUTTER_BIN" + +cd features/fixtures/mazerunner +$FLUTTER_BIN build apk --no-tree-shake-icons diff --git a/features/scripts/build_ios_app.sh b/features/scripts/build_ios_app.sh new file mode 100755 index 0000000..f6bb983 --- /dev/null +++ b/features/scripts/build_ios_app.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -o errexit + +if [ -z "$FLUTTER_BIN" ]; then + FLUTTER_BIN="flutter" +fi + +EXPORT_OPTIONS="$(pwd)/features/fixture_resources/exportOptions.plist" + +cd features/fixtures/mazerunner/ios + +$FLUTTER_BIN build ipa --export-options-plist=$EXPORT_OPTIONS --no-tree-shake-icons diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh new file mode 100755 index 0000000..df0aa52 --- /dev/null +++ b/features/scripts/generate_fixture.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -o errexit + +if [ -z "$FLUTTER_BIN" ]; then + FLUTTER_BIN="flutter" +fi + +FIXTURE_LOCATION=features/fixtures/mazerunner + +PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" + +EXPORT_OPTIONS=features/fixture_resources/exportOptions.plist + +XCODE_PROJECT=features/fixtures/mazerunner/ios/Runner.xcodeproj/project.pbxproj + +ANDROID_MANIFEST=features/fixtures/mazerunner/android/app/src/main/AndroidManifest.xml + +DART_LOCATION=features/fixtures/mazerunner/lib + +DART_TEST_LOCATION=features/fixtures/test + +BS_DART_LOCATION=features/fixture_resources/lib + +BS_DART_DESTINATION=features/fixtures/mazerunner + +echo "Remove old fixture" + +rm -rf $FIXTURE_LOCATION + +echo "Create blank fixture" + +$FLUTTER_BIN create $FIXTURE_LOCATION --org com.bugsnag --platforms=ios,android + +echo "Add dependencies" + +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_flutter_performance:{'path':'$PACKAGE_PATH'}" + +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" path_provider + +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" http + +echo "Add dev team to Xcode project" + +sed -i '' "s/ENABLE_BITCODE = NO;/ENABLE_BITCODE = NO;\nDEVELOPMENT_TEAM = 7W9PZ27Y5F;\nCODE_SIGN_STYLE = Automatic;/g" "$XCODE_PROJECT" + +echo "Add Android internet permission" + +sed -i '' "s/<\/application>/<\/application>\n/g" "$ANDROID_MANIFEST" + +echo "Copy test fixture code" + +rm -rf $DART_TEST_LOCATION + +rm -rf $DART_LOCATION + +cp -r $BS_DART_LOCATION $BS_DART_DESTINATION \ No newline at end of file diff --git a/features/scripts/save_fixture_changes.sh b/features/scripts/save_fixture_changes.sh new file mode 100755 index 0000000..84312e5 --- /dev/null +++ b/features/scripts/save_fixture_changes.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -o errexit + + +DART_LOCATION=features/fixtures/mazerunner/lib + +BS_DART_LOCATION=features/fixture_resources + +echo "Copy test fixture code" + +rm -rf "$BS_DART_LOCATION/lib" + +cp -r $DART_LOCATION $BS_DART_LOCATION \ No newline at end of file diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb new file mode 100644 index 0000000..85a57c6 --- /dev/null +++ b/features/steps/flutter_steps.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +When('I run {string}') do |scenario_name| + execute_command :run_scenario, scenario_name +end + +When("I run {string} and relaunch the crashed app") do |event_type| + step("I run \"#{event_type}\"") + step('I relaunch the app after a crash') +end + +When('I configure Bugsnag for {string}') do |scenario_name| + execute_command :start_bugsnag, scenario_name +end + +When('I configure the app to run in the {string} state') do |extra_config| + $extra_config = extra_config +end + +def execute_command(action, scenario_name) + extra_config = $extra_config || '' + command = { action: action, scenario_name: scenario_name, extra_config: extra_config } + Maze::Server.commands.add command + + touch_action = Appium::TouchAction.new + touch_action.tap({:x => 200, :y => 200}) + touch_action.perform + + $extra_config = '' + # Ensure fixture has read the command + count = 100 + sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1 + raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty? +end + +When('I relaunch the app') do + Maze.driver.launch_app +end + +When("I relaunch the app after a crash") do + # Wait for the app to stop running before relaunching + step 'the app is not running' + Maze.driver.launch_app +end + +Then('the app is not running') do + Maze::Wait.new(interval: 1, timeout: 20).until do + Maze.driver.app_state('com.bugsnag.flutter.test.app') == :not_running + end +end + +Then(/^on (Android|iOS), (.+)/) do |platform, step_text| + current_platform = Maze::Helper.get_current_platform + step(step_text) if current_platform.casecmp(platform).zero? +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..7fbcb1e --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +BeforeAll do + $api_key = 'abc12312312312312312312312312312' +end + +Before('@skip_android') do |_scenario| + skip_this_scenario('Not compatible with Android') if Maze::Helper.get_current_platform == 'android' +end + +Before('@skip_ios') do |_scenario| + skip_this_scenario('Not compatible with iOS') if Maze::Helper.get_current_platform == 'ios' +end From 2d6ba34d24c160565263c230d92d56d3a4bc011d Mon Sep 17 00:00:00 2001 From: Steve Kirkland-Walton Date: Thu, 26 Oct 2023 16:45:51 +0100 Subject: [PATCH 06/66] Allow BUILDKITE_JOB_ID in e2e container --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3fac688..7bfee9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: BUILDKITE_BUILD_CREATOR: BUILDKITE_BUILD_NUMBER: BUILDKITE_BUILD_URL: + BUILDKITE_JOB_ID: BUILDKITE_LABEL: BUILDKITE_MESSAGE: BUILDKITE_PIPELINE_NAME: From c689740c4e6d5bee406eeedd4a85980172e9f292 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:40:37 +0200 Subject: [PATCH 07/66] Clock implementation (#6) * [PLAT-10786] Clock implementation * Update packages/bugsnag_flutter_performance/lib/src/util/clock.dart Co-authored-by: Jason * Fixed clock * Renamed example in Makefile * Fixed paths in Makefile --------- Co-authored-by: Robert Co-authored-by: Jason --- .../lib/src/client.dart | 15 ++++++-- .../lib/src/span.dart | 4 ++- .../lib/src/uploader/uploader.dart | 5 ++- .../lib/src/util/clock.dart | 35 +++++++++++++++++++ .../test/src/client_test.dart | 6 ++-- .../test/src/span_test.dart | 10 ++++-- 6 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/util/clock.dart diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index e262be3..3250909 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -4,6 +4,7 @@ import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'configuration.dart'; import 'span.dart'; @@ -19,7 +20,15 @@ class BugsnagPerformanceClient implements BugsnagPerformance { BugsnagPerformanceConfiguration? configuration; Uploader? _uploader; SpanBatch? _currentBatch; - final PackageBuilder _packageBuilder = PackageBuilderImpl(); + late final PackageBuilder _packageBuilder; + late final BugsnagClock _clock; + + BugsnagPerformanceClient() { + BugsnagClockImpl.ensureInitialized(); + _packageBuilder = PackageBuilderImpl(); + _clock = BugsnagClockImpl.instance; + } + @override Future start({String? apiKey, Uri? endpoint}) async { configuration = BugsnagPerformanceConfiguration( @@ -33,11 +42,12 @@ class BugsnagPerformanceClient implements BugsnagPerformance { BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}) { final span = BugsnagPerformanceSpanImpl( name: name, - startTime: startTime ?? DateTime.now(), + startTime: startTime ?? _clock.now(), onEnded: (endedSpan) { _currentBatch?.add(endedSpan); }, ); + span.clock = _clock; if (configuration != null) { _currentBatch ??= SpanBatchImpl(); _currentBatch?.configure(configuration!); @@ -53,6 +63,7 @@ class BugsnagPerformanceClient implements BugsnagPerformance { apiKey: configuration!.apiKey!, url: configuration!.endpoint!, client: UploaderClientImpl(httpClient: HttpClient()), + clock: _clock, ); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 78c590c..555ff51 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -1,5 +1,6 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:bugsnag_flutter_performance/src/util/random.dart'; typedef TraceId = BigInt; @@ -36,6 +37,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { final DateTime startTime; DateTime? endTime; late final void Function(BugsnagPerformanceSpan) onEnded; + late final BugsnagClock clock; @override void end() { @@ -43,7 +45,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { return; } - endTime = DateTime.now(); + endTime = clock.now(); } BugsnagPerformanceSpanImpl.fromJson(Map json, diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart index f4162b7..febef95 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -1,5 +1,6 @@ import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; abstract class Uploader { Future upload({required OtlpPackage package}); @@ -9,10 +10,12 @@ class UploaderImpl implements Uploader { final String apiKey; final Uri url; final UploaderClient client; + final BugsnagClock clock; UploaderImpl({ required this.apiKey, required this.url, required this.client, + required this.clock, }); @override @@ -21,7 +24,7 @@ class UploaderImpl implements Uploader { }) async { var headers = { 'Bugsnag-Api-Key': apiKey, - 'Bugsnag-Sent-At': DateTime.now().toUtc().toIso8601String(), + 'Bugsnag-Sent-At': clock.now().toUtc().toIso8601String(), 'Bugsnag-Span-Sampling': '1:1' }; headers.addAll(package.headers); diff --git a/packages/bugsnag_flutter_performance/lib/src/util/clock.dart b/packages/bugsnag_flutter_performance/lib/src/util/clock.dart new file mode 100644 index 0000000..41f2215 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/util/clock.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; + +abstract class BugsnagClock { + DateTime now(); +} + +class BugsnagClockImpl extends BindingBase implements BugsnagClock { + static BugsnagClockImpl get instance => BindingBase.checkInstance(_instance); + static BugsnagClockImpl? _instance; + + final _clock = Stopwatch()..start(); + final _initTime = DateTime.now(); + + BugsnagClockImpl() { + _clock.start(); + } + + @override + DateTime now() { + return _initTime.add(_clock.elapsed); + } + + static BugsnagClock ensureInitialized() { + if (_instance == null) { + BugsnagClockImpl(); + } + return instance; + } + + @override + void initInstances() { + super.initInstances(); + _instance = this; + } +} diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index de84c96..1d4a819 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -1,11 +1,13 @@ import 'package:bugsnag_flutter_performance/src/client.dart'; import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const apiKey = 'TestApiKey'; final endpoint = Uri.tryParse('https://bugsnag.com')!; + BugsnagClockImpl.ensureInitialized(); group('BugsnagPerformanceClient', () { late BugsnagPerformanceClient client; @@ -26,9 +28,9 @@ void main() { test( 'should return a running span with the provided name and current time', () { - final timeBeforeStart = DateTime.now(); + final timeBeforeStart = BugsnagClockImpl.instance.now(); final span = client.startSpan(name) as BugsnagPerformanceSpanImpl; - final timeAfterStart = DateTime.now(); + final timeAfterStart = BugsnagClockImpl.instance.now(); expect(span.name, equals(name)); expect( diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index d21cd09..4f9d1a4 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -1,11 +1,14 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:bugsnag_flutter_performance/src/util/random.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const millisecondsSinceEpoch = 1640979000000; + BugsnagClockImpl.ensureInitialized(); + group('BugsnagPerformanceSpanImpl', () { test('should have the provided name, start time, traceId and spanId', () { final traceId = randomTraceId(); @@ -69,9 +72,10 @@ void main() { startTime: DateTime.fromMillisecondsSinceEpoch( millisecondsSinceEpoch, isUtc: true)); - final timeBeforeEnd = DateTime.now(); + span.clock = BugsnagClockImpl.instance; + final timeBeforeEnd = BugsnagClockImpl.instance.now(); span.end(); - final timeAfterEnd = DateTime.now(); + final timeAfterEnd = BugsnagClockImpl.instance.now(); expect( span.endTime!.nanosecondsSinceEpoch >= timeBeforeEnd.nanosecondsSinceEpoch, @@ -88,6 +92,7 @@ void main() { startTime: DateTime.fromMillisecondsSinceEpoch( millisecondsSinceEpoch, isUtc: true)); + span.clock = BugsnagClockImpl.instance; span.end(); final firstEndTime = span.endTime; await Future.delayed(const Duration(milliseconds: 10)); @@ -156,6 +161,7 @@ void main() { traceId: randomTraceId(), parentSpanId: randomSpanId(), ); + span.clock = BugsnagClockImpl.instance; span.end(); final json = span.toJson(); expect(json['name'], equals(span.name)); From 1acef440c05d277cb103ab47f3e74f17ae0f38b2 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 30 Oct 2023 10:37:53 +0100 Subject: [PATCH 08/66] PLAT-11141 add initial public static api (#8) * initial commit * review changes --- Makefile | 2 +- .../lib/bugsnag_flutter_performance.dart | 2 +- .../src/bugsnag_performance_public_api.dart | 24 +++++++++++++++++++ .../lib/src/client.dart | 6 ++--- .../test/src/client_test.dart | 4 ++-- 5 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart diff --git a/Makefile b/Makefile index a6df58a..585f6e1 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ test: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) test -r expanded --suppress-analytics format: - $(FLUTTER_BIN) format packages/bugsnag_flutter_performance example features/fixtures/app + dart format packages/bugsnag_flutter_performance example features/fixtures/app lint: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) analyze --suppress-analytics diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index ab70915..f67cf70 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -1,4 +1,4 @@ library bugsnag_flutter_performance; -export 'src/client.dart'; export 'src/span.dart' show BugsnagPerformanceSpan; +export 'src/bugsnag_performance_public_api.dart' show BugsnagPerformance; diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart new file mode 100644 index 0000000..d6cf4d9 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import '../bugsnag_flutter_performance.dart'; +import 'client.dart'; + +class BugsnagPerformance { + static final BugsnagPerformanceClientImpl _client = + BugsnagPerformanceClientImpl(); + + static Future start({String? apiKey, Uri? endpoint}) async { + return _client.start(apiKey: apiKey, endpoint: endpoint); + } + + static BugsnagPerformanceSpan startSpan(String name) { + return _client.startSpan(name); + } + + static void runApp({ + FutureOr Function()? runApp, + }) { + //TODO implement + // _client.runApp(runApp: runApp); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 3250909..15dc3c1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -11,19 +11,19 @@ import 'span.dart'; const _defaultEndpoint = 'https://otlp.bugsnag.com/v1/traces'; -abstract class BugsnagPerformance { +abstract class BugsnagPerformanceClient { Future start({String? apiKey, Uri? endpoint}); BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}); } -class BugsnagPerformanceClient implements BugsnagPerformance { +class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagPerformanceConfiguration? configuration; Uploader? _uploader; SpanBatch? _currentBatch; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; - BugsnagPerformanceClient() { + BugsnagPerformanceClientImpl() { BugsnagClockImpl.ensureInitialized(); _packageBuilder = PackageBuilderImpl(); _clock = BugsnagClockImpl.instance; diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index 1d4a819..ef91c0b 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -9,10 +9,10 @@ void main() { final endpoint = Uri.tryParse('https://bugsnag.com')!; BugsnagClockImpl.ensureInitialized(); group('BugsnagPerformanceClient', () { - late BugsnagPerformanceClient client; + late BugsnagPerformanceClientImpl client; setUp(() { - client = BugsnagPerformanceClient(); + client = BugsnagPerformanceClientImpl(); }); group('start', () { test('should set configuration with the provided parameters', () { From 847a1a76e29e531314c4043b133499bb0ae2bfdb Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:12:05 +0100 Subject: [PATCH 09/66] Manual span e2e test (#9) * [PLAT-11142] Create proper json payload structure in requests * [PLAT-10795] e2e tests for custom span delivery for Flutter Performance * Fixed the fixture * Set batch size to 1 for manual span e2e test * start bugsnag in scenario * manual url for debugging * disable gzip for debugging * Added the missing onEnded call to span * Fixed the payload and changed fixture api key * revert debug stuff * correct host * correct enspoint addition * temporarily disable sha check * temp debug url * temp disable gzip * enable integrity * removed debugging webhook * Fixed span formatting, sha1 digest, duplicate spans * Fixture fixes * Added empty attributes to span JSON * Added resource.attributes * Fixed traceId length * Added a hardcoded attribute * Added hardcoded resource attributes * Span changes * Small improvements * Small fixes * Added correct hardcoded values to resource attributes --------- Co-authored-by: Robert Co-authored-by: Richard Elms --- features/fixture_resources/lib/main.dart | 91 ++++++++++--------- .../lib/scenarios/manual_span_scenario.dart | 14 +-- .../lib/scenarios/scenarios.dart | 2 +- features/manual_span.feature | 12 ++- .../src/bugsnag_performance_public_api.dart | 4 + .../lib/src/client.dart | 1 - .../lib/src/span.dart | 16 ++-- .../lib/src/uploader/package_builder.dart | 46 ++++++++-- .../lib/src/uploader/uploader.dart | 5 +- .../lib/src/util/random.dart | 4 +- .../bugsnag_flutter_performance/pubspec.yaml | 1 + .../test/src/span_test.dart | 34 ++++--- 12 files changed, 146 insertions(+), 84 deletions(-) diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index 3f63aca..f106f14 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -27,10 +27,11 @@ extension StringGet on Map { return value is String ? value : null; } } -class FixtureConfig -{ + +class FixtureConfig { static Uri MAZE_HOST = Uri.parse(""); } + /// Represents a MazeRunner command class Command { final String action; @@ -43,8 +44,6 @@ class Command { required this.extraConfig, }); - - factory Command.fromJsonString(String jsonString) { final map = json.decode(jsonString) as Map; if (map['action'] == null) { @@ -69,51 +68,59 @@ class MazeRunnerFlutterApp extends StatelessWidget { theme: ThemeData( primaryColor: const Color.fromARGB(255, 73, 73, 227), ), - home: FutureBuilder( - future: Future(() async { - for (var i = 0; i < 30; i++) { - try { - final Directory directory = await appFilesDirectory(); - final File file = File('${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); - final text = await file.readAsString(); + home: FutureBuilder(future: Future(() async { + for (var i = 0; i < 30; i++) { + try { + final Directory directory = await appFilesDirectory(); + final File file = File( + '${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); + final text = await file.readAsString(); + print("fixture_config.json found with contents: $text"); + Map json = jsonDecode(text); + if (json.containsKey('maze_address')) { print("fixture_config.json found with contents: $text"); - Map json = jsonDecode(text); - if (json.containsKey('maze_address')) { - print("fixture_config.json found with contents: $text"); - FixtureConfig.MAZE_HOST = Uri.parse("http://" + json['maze_address']); - return json['maze_address']; - } - } catch (e) { - print("Couldn't read fixture_config.json: $e"); + FixtureConfig.MAZE_HOST = + Uri.parse("http://" + json['maze_address']); + return json['maze_address']; } - await Future.delayed(const Duration(seconds: 1)); - } - print("fixture_config.json not read within 30s, defaulting to BrowserStack address"); - FixtureConfig.MAZE_HOST = Uri.parse('bs-local.com:9339'); - return 'bs-local.com:9339'; - }), - builder: (_, mazerunnerUrl) { - if (mazerunnerUrl.data != null) { - return MazeRunnerHomePage(mazerunnerUrl: mazerunnerUrl.data!,); - } else { - return Container(color: Colors.white, child: const Center(child: CircularProgressIndicator())); + } catch (e) { + print("Couldn't read fixture_config.json: $e"); } + await Future.delayed(const Duration(seconds: 1)); + } + print( + "fixture_config.json not read within 30s, defaulting to BrowserStack address"); + FixtureConfig.MAZE_HOST = Uri.parse('bs-local.com:9339'); + return 'bs-local.com:9339'; + }), builder: (_, mazerunnerUrl) { + if (mazerunnerUrl.data != null) { + return MazeRunnerHomePage( + mazerunnerUrl: mazerunnerUrl.data!, + ); + } else { + return Container( + color: Colors.white, + child: const Center(child: CircularProgressIndicator())); + } }), ); } Future appFilesDirectory() async { if (Platform.isAndroid) { - return await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory(); + return await getExternalStorageDirectory() ?? + await getApplicationDocumentsDirectory(); } return await getApplicationDocumentsDirectory(); } - } class MazeRunnerHomePage extends StatefulWidget { final String mazerunnerUrl; - const MazeRunnerHomePage({Key? key, required this.mazerunnerUrl,}) : super(key: key); + const MazeRunnerHomePage({ + Key? key, + required this.mazerunnerUrl, + }) : super(key: key); @override State createState() => _HomePageState(); @@ -160,8 +167,7 @@ class _HomePageState extends State { final commandUrl = _commandEndpointController.value.text; - final response = await http - .get(Uri.parse(commandUrl)); + final response = await http.get(Uri.parse(commandUrl)); if (response.statusCode == 200) { // If the server did return a 200 OK response, @@ -171,7 +177,8 @@ class _HomePageState extends State { if (response.body.isEmpty) { log('Empty command, retrying...'); if (retry) { - Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)) + .then((value) => _onRunCommand(context, retry: true)); } return; } @@ -179,26 +186,22 @@ class _HomePageState extends State { _scenarioNameController.text = command.scenarioName; _extraConfigController.text = command.extraConfig; - switch (command.action) { case 'run_scenario': _onRunScenario(context); break; } - } - else - { + } else { if (retry) { - Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)) + .then((value) => _onRunCommand(context, retry: true)); } } - } /// Starts Bugsnag Future _onStartBugsnag() async { - log('Starting Bugsnag'); - // await BugsnagPerformance.start(); + } /// Runs a scenario, starting bugsnag first diff --git a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart index 73c88c3..0b07de2 100644 --- a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart @@ -1,4 +1,3 @@ - import 'dart:convert'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; @@ -7,13 +6,14 @@ import 'package:mazerunner/main.dart'; import 'scenario.dart'; import 'package:http/http.dart' as http; -class ManualSpan extends Scenario { +class ManualSpanScenario extends Scenario { @override Future run() async { - log("running manual span scenario"); - http.post(Uri.parse(FixtureConfig.MAZE_HOST.toString() + "/logs"), body: jsonEncode({ - "message": "manual span scenario" - })); + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + BugsnagPerformance.setBatchSize(1); + final span = BugsnagPerformance.startSpan('ManualSpanScenario'); + span.end(); } } - diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 24e7a35..55bcd1c 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -9,5 +9,5 @@ class ScenarioInfo { // Flutter obfuscation *requires* that we specify the name as a raw String in order to match the runtime class final List> scenarios = [ - ScenarioInfo('ManualSpan', () => ManualSpan()) + ScenarioInfo('ManualSpanScenario', () => ManualSpanScenario()) ]; diff --git a/features/manual_span.feature b/features/manual_span.feature index d47155b..a7f3f32 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -1,6 +1,12 @@ Feature: Manual Spans Scenario: Manual Span - When I run "ManualSpan" - And I wait to receive a log - And the log payload field "message" equals "manual span scenario" \ No newline at end of file + When I run "ManualSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "ManualSpanScenario" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index d6cf4d9..26570db 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -21,4 +21,8 @@ class BugsnagPerformance { //TODO implement // _client.runApp(runApp: runApp); } + + static void setBatchSize(int batchSize) { + _client.setBatchSize(batchSize); + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 15dc3c1..78121ec 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -52,7 +52,6 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _currentBatch ??= SpanBatchImpl(); _currentBatch?.configure(configuration!); _currentBatch?.onBatchFull = _sendBatch; - _currentBatch?.add(span); } return span; } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 555ff51..6da628c 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -46,14 +46,15 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { } endTime = clock.now(); + onEnded(this); } BugsnagPerformanceSpanImpl.fromJson(Map json, [void Function(BugsnagPerformanceSpan)? onEnded]) - : startTime = (json['startTimeUnixNano'] as int).timeFromNanos, + : startTime = int.parse(json['startTimeUnixNano']).timeFromNanos, name = json['name'] as String, endTime = json['endTimeUnixNano'] != null - ? (json['endTimeUnixNano'] as int).timeFromNanos + ? int.parse(json['endTimeUnixNano']).timeFromNanos : null, traceId = _decodeTraceId(json['traceId'] as String?) ?? randomTraceId(), spanId = _decodeSpanId(json['spanId'] as String?) ?? randomSpanId(), @@ -62,13 +63,16 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { @override dynamic toJson() => { - 'startTimeUnixNano': startTime.nanosecondsSinceEpoch, + 'startTimeUnixNano': startTime.nanosecondsSinceEpoch.toString(), 'name': name, - if (endTime != null) 'endTimeUnixNano': endTime!.nanosecondsSinceEpoch, + if (endTime != null) + 'endTimeUnixNano': endTime!.nanosecondsSinceEpoch.toString(), 'traceId': _encodeTraceId(traceId), 'spanId': _encodeSpanId(spanId), + 'kind': 1, if (parentSpanId != null) 'parentSpanId': _encodeSpanId(parentSpanId ?? BigInt.zero), + 'attributes': [], }; @override @@ -82,11 +86,11 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { } String _encodeSpanId(SpanId spanId) { - return spanId.toRadixString(16); + return spanId.toRadixString(16).padLeft(16, '0'); } String _encodeTraceId(TraceId traceId) { - return traceId.toRadixString(16); + return traceId.toRadixString(16).padLeft(32, '0'); } TraceId? _decodeTraceId(String? traceIdString) { diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index c659482..56b1a12 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; +import 'package:crypto/crypto.dart'; const int _minSizeForGzip = 128; @@ -18,14 +19,13 @@ class PackageBuilderImpl implements PackageBuilder { OtlpPackage build(List spans) { var payload = _buildPayload(spans: spans); var isZipped = false; - final uncompressedDataLength = payload.length; + final uncompressedData = payload; if (payload.length >= _minSizeForGzip) { payload = GZipCodec().encode(payload); isZipped = true; } final headers = _buildHeaders( - payload: payload, - uncompressedDataLength: uncompressedDataLength, + payload: uncompressedData, isZipped: isZipped, ); return OtlpPackage( @@ -38,19 +38,51 @@ class PackageBuilderImpl implements PackageBuilder { required List spans, }) { final jsonList = spans.map((span) => span.toJson()).toList(); - final json = jsonEncode(jsonList); + final jsonRequest = { + 'resourceSpans': [ + { + 'scopeSpans': [ + { + 'spans': jsonList, + } + ], + 'resource': { + 'attributes': [ + { + 'key': 'deployment.environment', + 'value': { + 'stringValue': 'staging', + } + }, + { + 'key': 'telemetry.sdk.name', + 'value': { + 'stringValue': 'bugsnag.performance.flutter', + } + }, + { + 'key': 'telemetry.sdk.version', + 'value': { + 'stringValue': '0.0.1', + } + } + ], + }, + } + ] + }; + final json = jsonEncode(jsonRequest); return utf8.encode(json); } Map _buildHeaders({ required List payload, - required int uncompressedDataLength, required bool isZipped, }) { return { 'Content-Type': 'application/json', 'Bugsnag-Integrity': _integrityDigestForData(payload: payload), - 'Bugsnag-Uncompressed-Content-Length': uncompressedDataLength.toString(), + 'Bugsnag-Uncompressed-Content-Length': payload.length.toString(), if (isZipped) 'Content-Encoding': 'gzip' }; } @@ -58,6 +90,6 @@ class PackageBuilderImpl implements PackageBuilder { String _integrityDigestForData({ required List payload, }) { - return 'sha1 ${payload.map((e) => e.toRadixString(16)).join()}'; + return 'sha1 ${sha1.convert(payload)}'; } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart index febef95..290ee88 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -22,9 +22,12 @@ class UploaderImpl implements Uploader { Future upload({ required OtlpPackage package, }) async { + final sentAtTime = clock.now().toUtc(); var headers = { 'Bugsnag-Api-Key': apiKey, - 'Bugsnag-Sent-At': clock.now().toUtc().toIso8601String(), + 'Bugsnag-Sent-At': sentAtTime + .subtract(Duration(microseconds: sentAtTime.microsecond)) + .toIso8601String(), 'Bugsnag-Span-Sampling': '1:1' }; headers.addAll(package.headers); diff --git a/packages/bugsnag_flutter_performance/lib/src/util/random.dart b/packages/bugsnag_flutter_performance/lib/src/util/random.dart index 87acd4c..0c71f37 100644 --- a/packages/bugsnag_flutter_performance/lib/src/util/random.dart +++ b/packages/bugsnag_flutter_performance/lib/src/util/random.dart @@ -9,9 +9,9 @@ BigInt randomTraceId() { } BigInt randomValue(int length) { - BigInt result = BigInt.zero; final random = Random.secure(); - for (var i = 0; i < length; i++) { + BigInt result = BigInt.from(random.nextInt(256) & 0xff); + for (var i = 1; i < length; i++) { int byte = random.nextInt(256); result = (result << 8) | BigInt.from(byte & 0xff); } diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 7e4d843..3b32446 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -8,6 +8,7 @@ environment: flutter: ">=3.3.0" dependencies: + crypto: ^3.0.3 flutter: sdk: flutter plugin_platform_interface: ^2.0.2 diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 4f9d1a4..585f059 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -111,20 +111,26 @@ void main() { 'startTimeUnixNano': DateTime.fromMillisecondsSinceEpoch( millisecondsSinceEpoch, isUtc: true) - .nanosecondsSinceEpoch, + .nanosecondsSinceEpoch + .toString(), 'endTimeUnixNano': DateTime.fromMillisecondsSinceEpoch(endTime, isUtc: true) - .nanosecondsSinceEpoch, + .nanosecondsSinceEpoch + .toString(), 'traceId': 'ffa74cc50baa432515e9b28fc4abf2cb', 'spanId': 'fa0e2d25f149f215', 'parentSpanId': '6293f00f47da54de', }; final span = BugsnagPerformanceSpanImpl.fromJson(json); expect(span.name, equals(name)); - expect(span.startTime, - equals((json['startTimeUnixNano'] as int).timeFromNanos)); - expect(span.endTime, - equals((json['endTimeUnixNano'] as int).timeFromNanos)); + expect( + span.startTime, + equals((int.parse(json['startTimeUnixNano'] as String)) + .timeFromNanos)); + expect( + span.endTime, + equals( + (int.parse(json['endTimeUnixNano'] as String)).timeFromNanos)); expect(span.traceId, equals(BigInt.tryParse(json['traceId'] as String, radix: 16)!)); expect(span.spanId, @@ -142,12 +148,15 @@ void main() { 'startTimeUnixNano': DateTime.fromMillisecondsSinceEpoch( millisecondsSinceEpoch, isUtc: true) - .nanosecondsSinceEpoch, + .nanosecondsSinceEpoch + .toString(), }; final span = BugsnagPerformanceSpanImpl.fromJson(json); expect(span.name, equals(name)); - expect(span.startTime, - equals((json['startTimeUnixNano'] as int).timeFromNanos)); + expect( + span.startTime, + equals( + int.parse(json['startTimeUnixNano'] as String).timeFromNanos)); expect(span.endTime, isNull); }); }); @@ -165,11 +174,12 @@ void main() { span.end(); final json = span.toJson(); expect(json['name'], equals(span.name)); - expect(json['startTimeUnixNano'], + expect(int.parse(json['startTimeUnixNano']), equals(span.startTime.nanosecondsSinceEpoch)); - expect(json['endTimeUnixNano'], + expect(int.parse(json['endTimeUnixNano']), equals(span.endTime!.nanosecondsSinceEpoch)); expect(json['traceId'], equals(span.traceId.toRadixString(16))); + expect(json['traceId'].toString().length, equals(32)); expect(json['spanId'], equals(span.spanId.toRadixString(16))); expect( json['parentSpanId'], equals(span.parentSpanId!.toRadixString(16))); @@ -183,7 +193,7 @@ void main() { isUtc: true)); final json = span.toJson(); expect(json['name'], equals(span.name)); - expect(json['startTimeUnixNano'], + expect(int.parse(json['startTimeUnixNano']), equals(span.startTime.nanosecondsSinceEpoch)); expect(json['endTimeUnixNano'], isNull); }); From b718137ad69d471c05fdb81b1e3283239a545eb2 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 2 Nov 2023 09:27:28 +0100 Subject: [PATCH 10/66] Removed BindingBase as the base class for the clock (#10) Co-authored-by: Robert --- .../lib/src/util/clock.dart | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/bugsnag_flutter_performance/lib/src/util/clock.dart b/packages/bugsnag_flutter_performance/lib/src/util/clock.dart index 41f2215..578053b 100644 --- a/packages/bugsnag_flutter_performance/lib/src/util/clock.dart +++ b/packages/bugsnag_flutter_performance/lib/src/util/clock.dart @@ -1,35 +1,21 @@ -import 'package:flutter/foundation.dart'; - abstract class BugsnagClock { DateTime now(); } -class BugsnagClockImpl extends BindingBase implements BugsnagClock { - static BugsnagClockImpl get instance => BindingBase.checkInstance(_instance); +class BugsnagClockImpl implements BugsnagClock { + static BugsnagClockImpl get instance => _instance!; static BugsnagClockImpl? _instance; final _clock = Stopwatch()..start(); final _initTime = DateTime.now(); - BugsnagClockImpl() { - _clock.start(); - } - @override DateTime now() { return _initTime.add(_clock.elapsed); } static BugsnagClock ensureInitialized() { - if (_instance == null) { - BugsnagClockImpl(); - } + _instance = _instance ?? BugsnagClockImpl(); return instance; } - - @override - void initInstances() { - super.initInstances(); - _instance = this; - } } From e16f35a1f144ec924f32053738c707b7d9b0a0c1 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Fri, 3 Nov 2023 14:02:14 +0100 Subject: [PATCH 11/66] [PLAT-10785] Created release scripts (#12) Co-authored-by: Robert --- Makefile | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Makefile b/Makefile index 585f6e1..aa86bdb 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,48 @@ clean: build: aar examples/bugsnag_performance_example +bump: +ifeq ($(VERSION),) + @$(error VERSION is not defined. Run with `make VERSION=number bump`) +endif + sed -i '' "s/## TBD/## $(VERSION) ($(shell date '+%Y-%m-%d'))/" CHANGELOG.md + sed -i '' "s/^version: .*/version: $(VERSION)/" packages/bugsnag_flutter_performance/pubspec.yaml + +prerelease: bump +ifeq ($(VERSION),) + @$(error VERSION is not defined. Run with `make VERSION=number prerelease`) +endif + @git checkout -b release-v$(VERSION) + @git add CHANGELOG.md packages/bugsnag_flutter_performance/pubspec.yaml + @git diff --exit-code || (echo "you have unstaged changes - Makefile may need updating to `git add` some more files"; exit 1) + @git commit -m "Release v$(VERSION)" + @git push origin release-v$(VERSION) + @open "https://github.com/bugsnag/bugsnag-flutter-performance/compare/main...release-v$(VERSION)?expand=1&title=Release%20v$(VERSION)&body="$$(awk 'start && /^## /{exit;};/^## /{start=1;next};start' CHANGELOG.md | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g') + +release: ## Releases the current main branch as $VERSION + @git fetch origin +ifneq ($(shell git rev-parse --abbrev-ref HEAD),main) # Check the current branch name + @git checkout main + @git rebase origin/main +endif +ifneq ($(shell git diff origin/main..main),) + $(error you have unpushed commits on the main branch) +endif + @git tag v$(PRESET_VERSION) + # Swift Package Manager prefers tags to be unprefixed package versions + @git tag $(PRESET_VERSION) + @git push origin v$(PRESET_VERSION) $(PRESET_VERSION) + @git checkout next + @git rebase origin/next + @git merge main + @git push origin next + # Prep GitHub release + # We could technically do a `hub release` here but a verification step + # before it goes live always seems like a good thing + @open 'https://github.com/bugsnag/bugsnag-flutter-performance/releases/new?title=v$(PRESET_VERSION)&tag=v$(PRESET_VERSION)&body='$$(awk 'start && /^## /{exit;};/^## /{start=1;next};start' CHANGELOG.md | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g') + @git clean -df + cd packages/bugsnag_flutter_performance && flutter pub publish + aar: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) build aar --suppress-analytics From 3c1e72a09c1d7915d0d04414e3a6b54cd240be23 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Fri, 3 Nov 2023 14:54:03 +0100 Subject: [PATCH 12/66] PLAT-10787 attributes (#11) --- .buildkite/pipeline.yml | 29 ++++ features/resource_attributes.feature | 52 ++++++ features/support/env.rb | 9 + .../lib/src/client.dart | 9 +- .../src/extensions/resource_attributes.dart | 164 ++++++++++++++++++ .../lib/src/uploader/package_builder.dart | 39 ++--- .../bugsnag_flutter_performance/pubspec.yaml | 2 + .../test/src/client_test.dart | 4 +- .../test/src/span_test.dart | 10 +- 9 files changed, 283 insertions(+), 35 deletions(-) create mode 100644 features/resource_attributes.feature create mode 100644 packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d032a26..b0ad78a 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -34,6 +34,34 @@ steps: artifacts#v1.5.0: upload: - "features/fixtures/mazerunner/build/ios/ipa/mazerunner.ipa" + + - label: ':bitbar: iOS 14 end-to-end tests' + depends_on: "ios-fixture-3-10-0" + timeout_in_minutes: 20 + env: + FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" + agents: + queue: opensource + plugins: + artifacts#v1.5.0: + download: "features/fixtures/mazerunner/build/ios/ipa/mazerunner.ipa" + upload: "maze_output/failed/**/*" + docker-compose#v4.7.0: + pull: maze-runner + run: maze-runner + service-ports: true + command: + - "--app=/app/features/fixtures/mazerunner/build/ios/ipa/mazerunner.ipa" + - "--farm=bb" + - "--device=IOS_14|IOS_15|IOS_16" + - "--no-tunnel" + - "--aws-public-ip" + - "--fail-fast" + concurrency: 25 + concurrency_group: 'bitbar' + concurrency_method: eager + + # # Android # @@ -63,6 +91,7 @@ steps: artifacts#v1.5.0: download: "features/fixtures/mazerunner/build/app/outputs/flutter-apk/app-release.apk" upload: "maze_output/failed/**/*" + upload: "maze_output/passed/**/*" docker-compose#v4.7.0: pull: maze-runner run: maze-runner diff --git a/features/resource_attributes.feature b/features/resource_attributes.feature new file mode 100644 index 0000000..16e7584 --- /dev/null +++ b/features/resource_attributes.feature @@ -0,0 +1,52 @@ +Feature: Resource Attributes + + Scenario: Common Attributes + When I run "ManualSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "ManualSpanScenario" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + + * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "development" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.flutter" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" exists + * the trace payload field "resourceSpans.0.resource" string attribute "device.model.identifier" exists + * the trace payload field "resourceSpans.0.resource" string attribute "service.version" equals "1.0.0" + * the trace payload field "resourceSpans.0.resource" string attribute "device.manufacturer" exists + * the trace payload field "resourceSpans.0.resource" string attribute "host.arch" exists + * the trace payload field "resourceSpans.0.resource" string attribute "os.version" exists + + @android_only + Scenario: Android Attributes + When I run "ManualSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "ManualSpanScenario" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.platform" equals "android" + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.version_code" equals "1" + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.device.android_api_version" exists + + @ios_only + Scenario: iOS Attributes + When I run "ManualSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "ManualSpanScenario" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.platform" equals "ios" + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "1" \ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb index 7fbcb1e..64fa485 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -8,6 +8,15 @@ skip_this_scenario('Not compatible with Android') if Maze::Helper.get_current_platform == 'android' end + +Before('@android_only') do |_scenario| + skip_this_scenario('Skipping scenario') unless Maze::Helper.get_current_platform == 'android' +end + Before('@skip_ios') do |_scenario| skip_this_scenario('Not compatible with iOS') if Maze::Helper.get_current_platform == 'ios' end + +Before('@ios_only') do |_scenario| + skip_this_scenario('Skipping scenario') unless Maze::Helper.get_current_platform == 'ios' +end \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 78121ec..e96e202 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; @@ -25,7 +26,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagPerformanceClientImpl() { BugsnagClockImpl.ensureInitialized(); - _packageBuilder = PackageBuilderImpl(); + _packageBuilder = PackageBuilderImpl( + attributesProvider: ResourceAttributesProviderImpl(), + ); _clock = BugsnagClockImpl.instance; } @@ -67,12 +70,12 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } } - void _sendBatch(SpanBatch batch) { + void _sendBatch(SpanBatch batch) async { final spans = batch.drain(); if (spans.isEmpty) { return; } - final package = _packageBuilder.build(spans); + final package = await _packageBuilder.build(spans); _uploader?.upload(package: package); } diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart new file mode 100644 index 0000000..2b3182d --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart @@ -0,0 +1,164 @@ +import 'package:device_info/device_info.dart'; +import 'dart:io'; +import 'package:package_info/package_info.dart'; + +abstract class ResourceAttributesProvider { + Future>> resourceAttributes(); +} + +class ResourceAttributesProviderImpl implements ResourceAttributesProvider { + List> _resourceAttributes = []; + bool _didInitializeAttributes = false; + + @override + Future>> resourceAttributes() async { + if (!_didInitializeAttributes) { + await _initializeResourceAttributes(); + _didInitializeAttributes = true; + } + return _resourceAttributes; + } + + Future _initializeResourceAttributes() async { + final deviceInfo = DeviceInfoPlugin(); + final packageInfo = await PackageInfo.fromPlatform(); + + final attributes = [ + { + 'key': 'deployment.environment', + 'value': { + 'stringValue': getDeploymentEnvironment(), + } + }, + { + 'key': 'telemetry.sdk.name', + 'value': { + 'stringValue': 'bugsnag.performance.flutter', + } + }, + { + 'key': 'telemetry.sdk.version', + 'value': { + 'stringValue': '0.0.1', + } + }, + { + "key": "device.model.identifier", + "value": { + "stringValue": await getDeviceModel(deviceInfo), + } + }, + { + "key": "service.version", + "value": { + "stringValue": packageInfo.version, + } + }, + { + "key": "bugsnag.app.platform", + "value": { + "stringValue": Platform.operatingSystem, + } + }, + { + "key": "device.manufacturer", + "value": { + "stringValue": await getDeviceManufacturer(deviceInfo), + } + }, + { + "key": "host.arch", + "value": { + "stringValue": await getDeviceArchitecture(deviceInfo), + } + }, + { + "key": "os.version", + "value": { + "stringValue": await getOSVersion(deviceInfo), + } + }, + { + "key": Platform.isAndroid + ? "bugsnag.app.version_code" + : "bugsnag.app.bundle_version", + "value": { + "stringValue": packageInfo.buildNumber, + } + } + // { + // "key": "device.id", + // "value": { + // "stringValue": GET FROM PERSISTENCE LAYER + // } + // } + // TODO add device.id once persistence is added + ]; + + // Add Android-specific attributes + if (Platform.isAndroid) { + attributes.add({ + "key": "bugsnag.device.android_api_version", + "value": { + "stringValue": await getAndroidAPILevel(deviceInfo), + } + }); + } + + _resourceAttributes = attributes; + } + + static Future getDeviceModel(DeviceInfoPlugin deviceInfo) async { + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + return androidInfo.model; + } else if (Platform.isIOS) { + final iosInfo = await deviceInfo.iosInfo; + return iosInfo.model; + } + return "Unknown"; + } + + static Future getDeviceManufacturer( + DeviceInfoPlugin deviceInfo) async { + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + return androidInfo.manufacturer; + } else if (Platform.isIOS) { + return "Apple"; + } + return "Unknown"; + } + + static Future getDeviceArchitecture( + DeviceInfoPlugin deviceInfo) async { + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + return androidInfo.supportedAbis[0]; + } else if (Platform.isIOS) { + return "arm64"; + } + return "Unknown"; + } + + static String getDeploymentEnvironment() { + final environment = Platform.environment['DEPLOYMENT_ENVIRONMENT']; + return environment ?? 'development'; + } + + static Future getAndroidAPILevel(DeviceInfoPlugin deviceInfo) async { + final androidInfo = await deviceInfo.androidInfo; + return androidInfo.version.sdkInt.toString(); + } + + static Future getOSVersion(DeviceInfoPlugin deviceInfo) async { + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + return androidInfo.version.release; + } else if (Platform.isIOS) { + final iosInfo = await deviceInfo.iosInfo; + return iosInfo.systemVersion; + } + return "Unknown"; + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index 56b1a12..56f21e2 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -1,23 +1,29 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; - import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; import 'package:crypto/crypto.dart'; +import '../extensions/resource_attributes.dart'; const int _minSizeForGzip = 128; abstract class PackageBuilder { - OtlpPackage build( + Future build( List spans, ); } class PackageBuilderImpl implements PackageBuilder { + final ResourceAttributesProvider attributesProvider; + + PackageBuilderImpl({ + required this.attributesProvider, + }); + @override - OtlpPackage build(List spans) { - var payload = _buildPayload(spans: spans); + Future build(List spans) async { + var payload = await _buildPayload(spans: spans); var isZipped = false; final uncompressedData = payload; if (payload.length >= _minSizeForGzip) { @@ -34,9 +40,9 @@ class PackageBuilderImpl implements PackageBuilder { ); } - List _buildPayload({ + Future> _buildPayload({ required List spans, - }) { + }) async { final jsonList = spans.map((span) => span.toJson()).toList(); final jsonRequest = { 'resourceSpans': [ @@ -47,26 +53,7 @@ class PackageBuilderImpl implements PackageBuilder { } ], 'resource': { - 'attributes': [ - { - 'key': 'deployment.environment', - 'value': { - 'stringValue': 'staging', - } - }, - { - 'key': 'telemetry.sdk.name', - 'value': { - 'stringValue': 'bugsnag.performance.flutter', - } - }, - { - 'key': 'telemetry.sdk.version', - 'value': { - 'stringValue': '0.0.1', - } - } - ], + 'attributes': await attributesProvider.resourceAttributes() }, } ] diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 3b32446..83bb3e5 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -12,6 +12,8 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 + device_info: ^2.0.3 + package_info: ^2.0.2 dev_dependencies: flutter_test: diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index ef91c0b..a0864cc 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -15,8 +15,8 @@ void main() { client = BugsnagPerformanceClientImpl(); }); group('start', () { - test('should set configuration with the provided parameters', () { - client.start(apiKey: apiKey, endpoint: endpoint); + test('should set configuration with the provided parameters', () async { + await client.start(apiKey: apiKey, endpoint: endpoint); expect(client.configuration!.apiKey, equals(apiKey)); expect(client.configuration!.endpoint, equals(endpoint)); diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 585f059..1fb8f91 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -178,11 +178,13 @@ void main() { equals(span.startTime.nanosecondsSinceEpoch)); expect(int.parse(json['endTimeUnixNano']), equals(span.endTime!.nanosecondsSinceEpoch)); - expect(json['traceId'], equals(span.traceId.toRadixString(16))); + expect(json['traceId'], + equals(span.traceId.toRadixString(16).padLeft(32, '0'))); expect(json['traceId'].toString().length, equals(32)); - expect(json['spanId'], equals(span.spanId.toRadixString(16))); - expect( - json['parentSpanId'], equals(span.parentSpanId!.toRadixString(16))); + expect(json['spanId'], + equals(span.spanId.toRadixString(16).padLeft(16, '0'))); + expect(json['parentSpanId'], + equals(span.parentSpanId!.toRadixString(16).padLeft(16, '0'))); }); test('should encode a running span', () { From 1dce715d4ca3ae99f17eecb6d09f764f3c16b81d Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:58:22 +0100 Subject: [PATCH 13/66] [PLAT-10787] Span attributes (#13) * [PLAT-10787] Span attributes * Fixed a json key * Fixed an E2E test * Fixed E2E tests * Fixed E2E tests --------- Co-authored-by: Robert --- features/manual_span.feature | 5 +- features/steps/flutter_steps.rb | 68 ++++++++++++++++ .../lib/src/span.dart | 9 ++- .../lib/src/span_attributes.dart | 78 +++++++++++++++++++ 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/span_attributes.dart diff --git a/features/manual_span.feature b/features/manual_span.feature index a7f3f32..e86e328 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -9,4 +9,7 @@ Feature: Manual Spans * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" \ No newline at end of file + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist + * every span string attribute "bugsnag.span.category" equals "custom" + * a span double attribute "bugsnag.sampling.p" equals 1.0 \ No newline at end of file diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index 85a57c6..f3a2f57 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -53,3 +53,71 @@ def execute_command(action, scenario_name) current_platform = Maze::Helper.get_current_platform step(step_text) if current_platform.casecmp(platform).zero? end + +Then('every span bool attribute {string} is false') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + spans.map { |span| Maze::check.false span['attributes'].find { |a| a['key'] == attribute }['value']['boolValue'] } +end + +Then('every span bool attribute {string} does not exist') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + spans.map { |span| Maze.check.nil span['attributes'].find { |a| a['key'] == attribute } } +end + +Then('every span string attribute {string} does not exist') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + spans.map { |span| Maze.check.nil span['attributes'].find { |a| a['key'] == attribute } } +end + +Then('all span bool attribute {string} is true') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('boolValue') } }.compact + selected_attributes.map { |a| Maze::check.true a['value']['boolValue'] } +end + +Then('a span bool attribute {string} is true') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('boolValue') } }.compact + selected_attributes = selected_attributes.map { |a| a['value']['boolValue'] == true } + Maze.check.false(selected_attributes.empty?) +end + +Then('all span bool attribute {string} is false') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('boolValue') } }.compact + selected_attributes.map { |a| Maze::check.false a['value']['boolValue'] } +end + +Then('a span bool attribute {string} is false') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('boolValue') } }.compact + selected_attributes = selected_attributes.map { |a| a['value']['boolValue'] == false } + Maze.check.false(selected_attributes.empty?) +end + +Then('a span bool attribute {string} does not exist') do |attribute| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_keys = spans.map { |span| !span['attributes'].find { |a| a['key'] == attribute } } + Maze.check.false(selected_keys.empty?) +end + +Then('a span string attribute {string} matches the regex {string}') do |attribute, pattern| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('stringValue') } }.compact + attribute_values = selected_attributes.map { |a| a['value']['stringValue'] } + attribute_values.map { |v| Maze.check.match pattern, v } +end + +Then('a span integer attribute {string} is greater than {int}') do |attribute, expected| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('intValue') } }.compact + attribute_values = selected_attributes.map { |a| a['value']['intValue'].to_i > expected } + Maze.check.false(attribute_values.empty?) +end + +Then('a span double attribute {string} equals {float}') do |attribute, value| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('doubleValue') } }.compact + selected_attributes = selected_attributes.map { |a| a['value']['doubleValue'] == value } + Maze.check.false(selected_attributes.empty?) +end diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 6da628c..d2e908a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -1,5 +1,6 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:bugsnag_flutter_performance/src/util/random.dart'; @@ -18,6 +19,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { BugsnagPerformanceSpanImpl({ required this.name, required this.startTime, + this.attributes = const BugsnagPerformanceSpanAttributes(), void Function(BugsnagPerformanceSpan)? onEnded, TraceId? traceId, SpanId? spanId, @@ -35,6 +37,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { @override SpanId? parentSpanId; final DateTime startTime; + final BugsnagPerformanceSpanAttributes attributes; DateTime? endTime; late final void Function(BugsnagPerformanceSpan) onEnded; late final BugsnagClock clock; @@ -59,7 +62,9 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { traceId = _decodeTraceId(json['traceId'] as String?) ?? randomTraceId(), spanId = _decodeSpanId(json['spanId'] as String?) ?? randomSpanId(), parentSpanId = _decodeSpanId(json['parentSpanId'] as String?), - onEnded = onEnded ?? _onEnded; + onEnded = onEnded ?? _onEnded, + attributes = + BugsnagPerformanceSpanAttributes.fromJson(json['attributes']); @override dynamic toJson() => { @@ -72,7 +77,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { 'kind': 1, if (parentSpanId != null) 'parentSpanId': _encodeSpanId(parentSpanId ?? BigInt.zero), - 'attributes': [], + 'attributes': attributes.toJson(), }; @override diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart new file mode 100644 index 0000000..cf54b85 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -0,0 +1,78 @@ +class BugsnagPerformanceSpanAttributes { + const BugsnagPerformanceSpanAttributes({ + this.category = 'custom', + this.isFirstClass, + this.samplingProbability = 1.0, + }); + + final String category; + final bool? isFirstClass; + final double samplingProbability; + + BugsnagPerformanceSpanAttributes.fromJson(dynamic json) + : category = _value( + json: json, + key: 'bugsnag.span.category', + type: _ParameterType.string, + ) as String? ?? + 'custom', + isFirstClass = _value( + json: json, + key: 'bugsnag.span.first_class', + type: _ParameterType.bool, + ) as bool?, + samplingProbability = _value( + json: json, + key: 'bugsnag.sampling.p', + type: _ParameterType.double, + ) as double? ?? + 1.0; + + dynamic toJson() => [ + { + 'key': 'bugsnag.span.category', + 'value': { + 'stringValue': category, + }, + }, + if (isFirstClass != null) + { + 'key': 'bugsnag.span.first_class', + 'value': { + 'boolValue': isFirstClass, + }, + }, + { + 'key': 'bugsnag.sampling.p', + 'value': { + 'doubleValue': samplingProbability, + }, + }, + ]; +} + +enum _ParameterType { string, double, bool } + +dynamic _value({ + required dynamic json, + required String key, + required _ParameterType type, +}) { + final attributes = json as List>?; + if (attributes == null) { + return null; + } + final entry = + attributes.where((element) => element['key'] == key).firstOrNull; + if (entry == null) { + return null; + } + switch (type) { + case _ParameterType.string: + return entry['value']['stringValue']; + case _ParameterType.double: + return entry['value']['doubleValue']; + case _ParameterType.bool: + return entry['value']['boolValue']; + } +} From cf02f9e7bc69fc66035514f17276025e452cf6a2 Mon Sep 17 00:00:00 2001 From: Steve Kirkland-Walton Date: Wed, 8 Nov 2023 11:46:40 +0000 Subject: [PATCH 14/66] Set BitBar dashboard project correctly --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3fac688..b7a6edc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: BUILDKITE_LABEL: BUILDKITE_MESSAGE: BUILDKITE_PIPELINE_NAME: + BUILDKITE_PIPELINE_SLUG: BUILDKITE_REPO: BUILDKITE_RETRY_COUNT: BUILDKITE_STEP_KEY: From 27938fa7c8537299c5ea3207e1c10f19f8df54b9 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Fri, 17 Nov 2023 10:16:09 +0100 Subject: [PATCH 15/66] PLAT-11193 device id persistence (#15) --- .../bugsnag_performance_example/lib/main.dart | 17 +- .../bugsnag_performance_example/pubspec.yaml | 2 +- features/persistence.feature | 8 + features/steps/flutter_steps.rb | 24 +++ .../lib/src/device_id_manager.dart | 151 ++++++++++++++++++ .../src/extensions/resource_attributes.dart | 15 +- .../bugsnag_flutter_performance/pubspec.yaml | 2 + 7 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 features/persistence.feature create mode 100644 packages/bugsnag_flutter_performance/lib/src/device_id_manager.dart diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index e479536..33b6502 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -1,9 +1,13 @@ +import 'package:bugsnag_flutter/bugsnag_flutter.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:flutter/material.dart'; const apiKey = 'add_your_api_key_here'; -void main() { +Future main() async { + await bugsnag.start(apiKey: '227df1042bc7772c321dbde3b31a03c2'); runApp(const MainApp()); + } class MainApp extends StatelessWidget { @@ -14,9 +18,18 @@ class MainApp extends StatelessWidget { return const MaterialApp( home: Scaffold( body: Center( - child: Text('Hello World!'), + child: TextButton(onPressed: startTestsSpan, child: Text('send test spans')) ), ), ); } + + } + +void startTestsSpan() { + BugsnagPerformance.start(apiKey: apiKey); + for(var i = 0; i < 200; i++) { + BugsnagPerformance.startSpan("test " + i.toString()).end(); + } +} \ No newline at end of file diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml index 3fcb6b5..63969ff 100644 --- a/examples/bugsnag_performance_example/pubspec.yaml +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: cupertino_icons: ^1.0.2 http: ^0.13.4 - + bugsnag_flutter: ^3.0.0 dev_dependencies: flutter_test: sdk: flutter diff --git a/features/persistence.feature b/features/persistence.feature new file mode 100644 index 0000000..fd0b30f --- /dev/null +++ b/features/persistence.feature @@ -0,0 +1,8 @@ +Feature: Persistence + + Scenario: Device Id Persists Between Launches + When I run "ManualSpanScenario" + * I relaunch the app + * I run "ManualSpanScenario" + * I wait to receive 2 traces + * every trace deviceid is valid and the same diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index f3a2f57..47a4d05 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -121,3 +121,27 @@ def execute_command(action, scenario_name) selected_attributes = selected_attributes.map { |a| a['value']['doubleValue'] == value } Maze.check.false(selected_attributes.empty?) end + +When('every trace deviceid is valid and the same') do + list = Maze::Server.list_for 'trace' + resource_spans = Maze::Helper.read_key_path(list.current[:body], 'resourceSpans') + + # Extract and validate device IDs + device_ids = resource_spans.map do |resource_span| + attributes = resource_span.dig('resource', 'attributes') + device_id_attribute = attributes.find { |a| a['key'] == 'device.id' } + device_id_value = device_id_attribute&.dig('value', 'stringValue') + + # Check device ID length and validity + valid_device_id = device_id_value.is_a?(String) && device_id_value.length == 32 + + # If device ID is not valid, you might want to handle it appropriately + raise "Invalid device ID: #{device_id_value}" unless valid_device_id + + device_id_value + end + + # Check if all device IDs are the same + Maze.check.true(device_ids.uniq.length == 1) +end + diff --git a/packages/bugsnag_flutter_performance/lib/src/device_id_manager.dart b/packages/bugsnag_flutter_performance/lib/src/device_id_manager.dart new file mode 100644 index 0000000..2493f29 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/device_id_manager.dart @@ -0,0 +1,151 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:package_info/package_info.dart'; +import 'package:uuid/uuid.dart'; +import 'package:path_provider/path_provider.dart'; + +class AndroidDeviceIdModel { + String id; + + AndroidDeviceIdModel({required this.id}); + + factory AndroidDeviceIdModel.fromJson(Map json) { + return AndroidDeviceIdModel(id: json['id']); + } + + Map toJson() { + return {'id': id}; + } +} + +class IosDeviceIdModel { + String deviceID; + + IosDeviceIdModel({required this.deviceID}); + + factory IosDeviceIdModel.fromJson(Map json) { + return IosDeviceIdModel(deviceID: json['deviceID']); + } + + Map toJson() { + return {'deviceID': deviceID}; + } +} + +abstract class DeviceIdManager { + Future getDeviceId(); +} + +class DeviceIdManagerImp extends DeviceIdManager { + final _androidDeviceIdFileName = '/device-id'; + final _iosDeviceIdFileName = '/device-id.json'; + String _androidDeviceIdFilePath = ''; + String _iosDeviceIdFilePath = ''; + + @override + Future getDeviceId() async { + String deviceId = await _readDeviceIdFile(); + if (deviceId.isEmpty) { + await _createNewDeviceId(); + deviceId = await _readDeviceIdFile(); + } + return deviceId; + } + + Future _getDeviceIdFilePath() async { + if (Platform.isAndroid) { + return await _getAndroidDeviceIdFilePath(); + } else if (Platform.isIOS) { + return await _getIosDeviceIdFilePath(); + } + return ''; + } + + Future _getAndroidDeviceIdFilePath() async { + if (_androidDeviceIdFilePath.isNotEmpty) { + return _androidDeviceIdFilePath; + } + final directory = await getApplicationSupportDirectory(); + _androidDeviceIdFilePath = directory.path + _androidDeviceIdFileName; + return _androidDeviceIdFilePath; + } + + Future _getIosDeviceIdFilePath() async { + if (_iosDeviceIdFilePath.isNotEmpty) { + return _iosDeviceIdFilePath; + } + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + final directory = await getApplicationSupportDirectory(); + _iosDeviceIdFilePath = + '${directory.path}/bugsnag-shared-${packageInfo.packageName}$_iosDeviceIdFileName'; + return _iosDeviceIdFilePath; + } + + Future _readDeviceIdFile() async { + try { + final path = await _getDeviceIdFilePath(); + final file = File(path); + bool fileExists = await file.exists(); + + if (!fileExists) { + return ''; + } + + final fileContents = await file.readAsString(); + + return fileContents.isEmpty ? '' : _getDeviceIdFromJson(fileContents); + } catch (e) { + //print('Error reading device ID file: $e'); + return ''; + } + } + + String _getDeviceIdFromJson(String json) { + try { + Map jsonMap = jsonDecode(json); + if (Platform.isIOS) { + return IosDeviceIdModel.fromJson(jsonMap).deviceID; + } else if (Platform.isAndroid) { + return AndroidDeviceIdModel.fromJson(jsonMap).id; + } + return ''; + } catch (e) { + //print('Error decoding JSON: $e'); + return ''; + } + } + + String _generateNewDeviceId() { + return const Uuid().v4().replaceAll('-', ''); + } + + Future _createNewDeviceId() async { + final deviceId = _generateNewDeviceId(); + final deviceModel = Platform.isAndroid + ? AndroidDeviceIdModel(id: deviceId) + : IosDeviceIdModel(deviceID: deviceId); + try { + final json = jsonEncode(deviceModel); + await _writeDeviceIdFile(json); + } catch (e) { + //print('Error encoding JSON: $e'); + } + } + + Future _writeDeviceIdFile(String json) async { + try { + final path = await _getDeviceIdFilePath(); + final file = File(path); + + // Create directory if it doesn't exist + final directory = Directory(file.parent.path); + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + + await file.writeAsString(json); + } catch (e) { + //print('Error writing device ID file: $e'); + } + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart index 2b3182d..ff43fc8 100644 --- a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart @@ -1,6 +1,7 @@ import 'package:device_info/device_info.dart'; import 'dart:io'; import 'package:package_info/package_info.dart'; +import 'package:bugsnag_flutter_performance/src/device_id_manager.dart'; abstract class ResourceAttributesProvider { Future>> resourceAttributes(); @@ -9,6 +10,7 @@ abstract class ResourceAttributesProvider { class ResourceAttributesProviderImpl implements ResourceAttributesProvider { List> _resourceAttributes = []; bool _didInitializeAttributes = false; + final DeviceIdManager _deviceIdManager = DeviceIdManagerImp(); @override Future>> resourceAttributes() async { @@ -85,14 +87,13 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { "value": { "stringValue": packageInfo.buildNumber, } + }, + { + "key": "device.id", + "value": { + "stringValue": await _deviceIdManager.getDeviceId(), + } } - // { - // "key": "device.id", - // "value": { - // "stringValue": GET FROM PERSISTENCE LAYER - // } - // } - // TODO add device.id once persistence is added ]; // Add Android-specific attributes diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 83bb3e5..af3af19 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: plugin_platform_interface: ^2.0.2 device_info: ^2.0.3 package_info: ^2.0.2 + path_provider: ^2.0.2 + uuid: ^4.0.0 dev_dependencies: flutter_test: From 1ac890a6344a20e1ab6565a86eaa0227ac7c0d94 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 21 Nov 2023 09:58:47 +0100 Subject: [PATCH 16/66] [PLAT-11228] Configure sampling on client side with static p-value (#17) Co-authored-by: Robert --- Makefile | 2 +- .../bugsnag_performance_example/lib/main.dart | 11 ++---- features/fixture_resources/lib/channels.dart | 1 - features/fixture_resources/lib/main.dart | 4 +- features/manual_span.feature | 1 + .../lib/src/client.dart | 13 ++++++- .../lib/src/span.dart | 11 +++++- .../lib/src/span_attributes.dart | 4 +- .../lib/src/uploader/package_builder.dart | 23 ++++++++++- .../lib/src/uploader/sampler.dart | 38 +++++++++++++++++++ .../lib/src/uploader/uploader.dart | 3 ++ 11 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart diff --git a/Makefile b/Makefile index aa86bdb..e514611 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ test: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) test -r expanded --suppress-analytics format: - dart format packages/bugsnag_flutter_performance example features/fixtures/app + dart format packages/bugsnag_flutter_performance examples features/fixture_resources/lib lint: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) analyze --suppress-analytics diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index 33b6502..fc429db 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -7,7 +7,6 @@ const apiKey = 'add_your_api_key_here'; Future main() async { await bugsnag.start(apiKey: '227df1042bc7772c321dbde3b31a03c2'); runApp(const MainApp()); - } class MainApp extends StatelessWidget { @@ -18,18 +17,16 @@ class MainApp extends StatelessWidget { return const MaterialApp( home: Scaffold( body: Center( - child: TextButton(onPressed: startTestsSpan, child: Text('send test spans')) - ), + child: TextButton( + onPressed: startTestsSpan, child: Text('send test spans'))), ), ); } - - } void startTestsSpan() { BugsnagPerformance.start(apiKey: apiKey); - for(var i = 0; i < 200; i++) { + for (var i = 0; i < 200; i++) { BugsnagPerformance.startSpan("test " + i.toString()).end(); } -} \ No newline at end of file +} diff --git a/features/fixture_resources/lib/channels.dart b/features/fixture_resources/lib/channels.dart index ac4ef2a..d270452 100644 --- a/features/fixture_resources/lib/channels.dart +++ b/features/fixture_resources/lib/channels.dart @@ -20,5 +20,4 @@ class MazeRunnerChannels { 'scenarioName': scenarioName, ...?arguments, }); - } diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index f106f14..bd14c8b 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -200,9 +200,7 @@ class _HomePageState extends State { } /// Starts Bugsnag - Future _onStartBugsnag() async { - - } + Future _onStartBugsnag() async {} /// Runs a scenario, starting bugsnag first void _onRunScenario(BuildContext context) async { diff --git a/features/manual_span.feature b/features/manual_span.feature index e86e328..f54f3ad 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -5,6 +5,7 @@ Feature: Manual Spans And I wait for 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" * every span field "name" equals "ManualSpanScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index e96e202..4b27023 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/sampler.dart'; import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; @@ -21,6 +22,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagPerformanceConfiguration? configuration; Uploader? _uploader; SpanBatch? _currentBatch; + Sampler? _sampler; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; @@ -47,7 +49,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { name: name, startTime: startTime ?? _clock.now(), onEnded: (endedSpan) { - _currentBatch?.add(endedSpan); + if (_sampler?.sample(endedSpan) ?? true) { + _currentBatch?.add(endedSpan); + } }, ); span.clock = _clock; @@ -60,18 +64,23 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } void _setup() { + _sampler = SamplerImpl(); if (configuration?.endpoint != null && configuration?.apiKey != null) { _uploader = UploaderImpl( apiKey: configuration!.apiKey!, url: configuration!.endpoint!, client: UploaderClientImpl(httpClient: HttpClient()), clock: _clock, + sampler: _sampler!, ); } } void _sendBatch(SpanBatch batch) async { - final spans = batch.drain(); + var spans = batch.drain(); + if (_sampler != null) { + spans = _sampler!.sampled(spans); + } if (spans.isEmpty) { return; } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index d2e908a..0ffd104 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -19,15 +19,16 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { BugsnagPerformanceSpanImpl({ required this.name, required this.startTime, - this.attributes = const BugsnagPerformanceSpanAttributes(), void Function(BugsnagPerformanceSpan)? onEnded, TraceId? traceId, SpanId? spanId, + BugsnagPerformanceSpanAttributes? attributes, this.parentSpanId, }) { this.traceId = traceId ?? randomTraceId(); this.spanId = spanId ?? randomSpanId(); this.onEnded = onEnded ?? _onEnded; + this.attributes = attributes ?? BugsnagPerformanceSpanAttributes(); } final String name; @override @@ -37,7 +38,7 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { @override SpanId? parentSpanId; final DateTime startTime; - final BugsnagPerformanceSpanAttributes attributes; + late final BugsnagPerformanceSpanAttributes attributes; DateTime? endTime; late final void Function(BugsnagPerformanceSpan) onEnded; late final BugsnagClock clock; @@ -88,6 +89,12 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { @override int get hashCode => toJson().hashCode; + + void updateSamplingProbability(double samplingProbability) { + if (samplingProbability < attributes.samplingProbability) { + attributes.samplingProbability = samplingProbability; + } + } } String _encodeSpanId(SpanId spanId) { diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index cf54b85..1c3e42b 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -1,5 +1,5 @@ class BugsnagPerformanceSpanAttributes { - const BugsnagPerformanceSpanAttributes({ + BugsnagPerformanceSpanAttributes({ this.category = 'custom', this.isFirstClass, this.samplingProbability = 1.0, @@ -7,7 +7,7 @@ class BugsnagPerformanceSpanAttributes { final String category; final bool? isFirstClass; - final double samplingProbability; + double samplingProbability; BugsnagPerformanceSpanAttributes.fromJson(dynamic json) : category = _value( diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index 56f21e2..cb12ef0 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; import 'package:crypto/crypto.dart'; import '../extensions/resource_attributes.dart'; @@ -31,6 +31,7 @@ class PackageBuilderImpl implements PackageBuilder { isZipped = true; } final headers = _buildHeaders( + spans: spans, payload: uncompressedData, isZipped: isZipped, ); @@ -63,6 +64,7 @@ class PackageBuilderImpl implements PackageBuilder { } Map _buildHeaders({ + required List spans, required List payload, required bool isZipped, }) { @@ -70,6 +72,7 @@ class PackageBuilderImpl implements PackageBuilder { 'Content-Type': 'application/json', 'Bugsnag-Integrity': _integrityDigestForData(payload: payload), 'Bugsnag-Uncompressed-Content-Length': payload.length.toString(), + 'Bugsnag-Span-Sampling': _samplingHeaderValue(spans: spans), if (isZipped) 'Content-Encoding': 'gzip' }; } @@ -79,4 +82,22 @@ class PackageBuilderImpl implements PackageBuilder { }) { return 'sha1 ${sha1.convert(payload)}'; } + + String _samplingHeaderValue({ + required List spans, + }) { + Map spansWithProbability = {}; + for (var element in spans) { + if (element is BugsnagPerformanceSpanImpl) { + final spansCount = + spansWithProbability[element.attributes.samplingProbability] ?? 0; + spansWithProbability[element.attributes.samplingProbability] = + spansCount + 1; + } + } + return spansWithProbability.entries + .map((e) => + '${e.key.toStringAsFixed(2).replaceFirst('0.', '.').replaceAll(RegExp(r"([.]*0+)(?!.*\d)"), "")}:${e.value}') + .join(';'); + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart new file mode 100644 index 0000000..bae8d16 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart @@ -0,0 +1,38 @@ +import 'package:bugsnag_flutter_performance/src/span.dart'; + +abstract class Sampler { + bool sample(BugsnagPerformanceSpan span); + List sampled(List spans); +} + +class SamplerImpl implements Sampler { + double samplingProbability; + + static final BigInt baselineInt = + BigInt.parse('18446744073709551', radix: 10); + + SamplerImpl({ + this.samplingProbability = 1.0, + }); + + @override + bool sample(BugsnagPerformanceSpan span) { + var isSampled = false; + if (samplingProbability == 1.0) { + isSampled = true; + } else if (samplingProbability > 0.0) { + isSampled = span.traceId.toUnsigned(64) <= + baselineInt * BigInt.from(samplingProbability * 1000); + } + if (isSampled) { + (span as BugsnagPerformanceSpanImpl?) + ?.updateSamplingProbability(samplingProbability); + } + return isSampled; + } + + @override + List sampled(List spans) { + return spans.where((element) => sample(element)).toList(); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart index 290ee88..d0dbd0d 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -1,3 +1,4 @@ +import 'package:bugsnag_flutter_performance/src/uploader/sampler.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; @@ -11,11 +12,13 @@ class UploaderImpl implements Uploader { final Uri url; final UploaderClient client; final BugsnagClock clock; + final Sampler sampler; UploaderImpl({ required this.apiKey, required this.url, required this.client, required this.clock, + required this.sampler, }); @override From b3e3111b38678258b5d4f88636f62d050a486ed4 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Tue, 21 Nov 2023 11:12:41 +0100 Subject: [PATCH 17/66] PLAT-11194 implement retry queue (#16) --- .../bugsnag_performance_example/lib/main.dart | 2 - .../bugsnag_performance_example/pubspec.yaml | 1 - features/fixture_resources/lib/main.dart | 18 ++- .../lib/scenarios/scenarios.dart | 4 +- .../lib/scenarios/start_sdk_default.dart | 12 ++ features/persistence.feature | 18 +++ features/steps/flutter_steps.rb | 8 ++ .../lib/src/client.dart | 12 +- .../lib/src/uploader/retry_queue.dart | 114 ++++++++++++++++++ .../lib/src/uploader/retry_queue_builder.dart | 13 ++ .../lib/src/uploader/uploader.dart | 43 +++++-- .../test/src/client_test.dart | 21 ++++ 12 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/start_sdk_default.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue_builder.dart diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index fc429db..a04ff14 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -1,11 +1,9 @@ -import 'package:bugsnag_flutter/bugsnag_flutter.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:flutter/material.dart'; const apiKey = 'add_your_api_key_here'; Future main() async { - await bugsnag.start(apiKey: '227df1042bc7772c321dbde3b31a03c2'); runApp(const MainApp()); } diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml index 63969ff..6bff1b6 100644 --- a/examples/bugsnag_performance_example/pubspec.yaml +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -32,7 +32,6 @@ dependencies: cupertino_icons: ^1.0.2 http: ^0.13.4 - bugsnag_flutter: ^3.0.0 dev_dependencies: flutter_test: sdk: flutter diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index bd14c8b..cd4ef58 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -185,8 +185,12 @@ class _HomePageState extends State { final command = Command.fromJsonString(response.body); _scenarioNameController.text = command.scenarioName; _extraConfigController.text = command.extraConfig; + print("Doing Action: ${command.action}"); switch (command.action) { + case 'clear_cache': + await _clearPersistentData(); + break; case 'run_scenario': _onRunScenario(context); break; @@ -199,6 +203,18 @@ class _HomePageState extends State { } } + Future _clearPersistentData() async { + print("Should clear the cache"); + final appCacheDir = await getApplicationSupportDirectory(); + try { + await Directory('${appCacheDir.path}/bugsnag-performance') + .delete(recursive: true); + print("Clear cache complete"); + } catch (e) { + print("Couldn't delete bugsnag-performance directory: $e"); + } + } + /// Starts Bugsnag Future _onStartBugsnag() async {} @@ -208,8 +224,6 @@ class _HomePageState extends State { if (scenario == null) { return; } - //TODO: Clear persistent data - // await scenario.clearPersistentData(); scenario.extraConfig = _extraConfigController.value.text; diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 55bcd1c..335a916 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,4 +1,5 @@ import 'manual_span_scenario.dart'; +import 'start_sdk_default.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -9,5 +10,6 @@ class ScenarioInfo { // Flutter obfuscation *requires* that we specify the name as a raw String in order to match the runtime class final List> scenarios = [ - ScenarioInfo('ManualSpanScenario', () => ManualSpanScenario()) + ScenarioInfo('ManualSpanScenario', () => ManualSpanScenario()), + ScenarioInfo('StartSdkDefault', () => StartSdkDefault()) ]; diff --git a/features/fixture_resources/lib/scenarios/start_sdk_default.dart b/features/fixture_resources/lib/scenarios/start_sdk_default.dart new file mode 100644 index 0000000..3b90637 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/start_sdk_default.dart @@ -0,0 +1,12 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:mazerunner/main.dart'; +import 'scenario.dart'; + +class StartSdkDefault extends Scenario { + @override + Future run() async { + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + } +} diff --git a/features/persistence.feature b/features/persistence.feature index fd0b30f..4a5ab10 100644 --- a/features/persistence.feature +++ b/features/persistence.feature @@ -1,8 +1,26 @@ Feature: Persistence +Background: + Given I clear the Bugsnag cache + Scenario: Device Id Persists Between Launches When I run "ManualSpanScenario" * I relaunch the app * I run "ManualSpanScenario" * I wait to receive 2 traces * every trace deviceid is valid and the same + + + Scenario: Receive a persisted trace + When I set the HTTP status code to 408 + * I run "ManualSpanScenario" + * I wait for 1 span + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.spanId" is stored as the value "original_span_id" + * I wait for requests to persist + * I discard the oldest trace + * I set the HTTP status code to 200 + * I relaunch the app + * I run "StartSdkDefault" + * I wait for 1 span + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.spanId" equals the stored value "original_span_id" + diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index 47a4d05..9ed6d13 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -1,5 +1,13 @@ # frozen_string_literal: true +When('I clear the Bugsnag cache') do + execute_command "clear_cache","" +end + +When('I wait for requests to persist') do + sleep 2 +end + When('I run {string}') do |scenario_name| execute_command :run_scenario, scenario_name end diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 4b27023..163a5c1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/retry_queue_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/sampler.dart'; import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; @@ -20,13 +22,16 @@ abstract class BugsnagPerformanceClient { class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagPerformanceConfiguration? configuration; + late RetryQueueBuilder retryQueueBuilder; Uploader? _uploader; SpanBatch? _currentBatch; + RetryQueue? _retryQueue; Sampler? _sampler; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; BugsnagPerformanceClientImpl() { + retryQueueBuilder = RetryQueueBuilderImpl(); BugsnagClockImpl.ensureInitialized(); _packageBuilder = PackageBuilderImpl( attributesProvider: ResourceAttributesProviderImpl(), @@ -41,6 +46,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { endpoint: endpoint ?? Uri.parse(_defaultEndpoint), ); _setup(); + await _retryQueue?.flush(); } @override @@ -73,6 +79,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { clock: _clock, sampler: _sampler!, ); + _retryQueue = retryQueueBuilder.build(_uploader!); } } @@ -85,7 +92,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { return; } final package = await _packageBuilder.build(spans); - _uploader?.upload(package: package); + final result = await _uploader?.upload(package: package); + if (result == RequestResult.retriableFailure) { + _retryQueue?.enqueue(headers: package.headers, body: package.payload); + } } void setBatchSize(int batchSize) { diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue.dart new file mode 100644 index 0000000..1a4bfd5 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:path_provider/path_provider.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; + +import '../util/clock.dart'; +import 'model/otlp_package.dart'; + +class CachedPayloadModel { + Map headers; + Uint8List body; + + CachedPayloadModel({required this.headers, required this.body}); + + factory CachedPayloadModel.fromJson(Map json) { + dynamic headersJson = json['headers']; + Map headers = Map.from(headersJson + .map((key, value) => MapEntry(key, value.toString()))); + + return CachedPayloadModel( + headers: headers, + body: base64Decode(json['body']), + ); + } + + Map toJson() { + return {'headers': headers, 'body': base64Encode(body)}; + } +} + +abstract class RetryQueue { + Future enqueue({ + required Map headers, + required Uint8List body, + }); + + Future flush(); +} + +class FileRetryQueue implements RetryQueue { + static const String _cacheDirectoryName = 'bugsnag-performance/v1/batches'; + static const Duration _maxAge = Duration(hours: 24); + + final Uploader? _uploader; + + FileRetryQueue(Uploader uploader) : _uploader = uploader; + + @override + Future enqueue({ + required Map headers, + required Uint8List body, + }) async { + final payload = + jsonEncode(CachedPayloadModel(headers: headers, body: body)); + final fileName = _generateFileName(); + await _writeToFile(fileName, payload); + } + + @override + Future flush() async { + final cacheDirectory = await _getCacheDirectory(); + if (!cacheDirectory.existsSync()) { + return; + } + + final files = cacheDirectory.listSync().cast() + ..sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync())); + + final now = BugsnagClockImpl.instance.now(); + for (var file in files) { + if (now.difference(file.lastModifiedSync()) > _maxAge) { + file.deleteSync(); + } else { + final payload = await file.readAsString(); + final cachedPayloadModel = + CachedPayloadModel.fromJson(jsonDecode(payload)); + final success = await _sendPayload(cachedPayloadModel); + if (success) { + file.deleteSync(); + } + } + } + } + + Future _sendPayload(CachedPayloadModel payloadModel) async { + final package = OtlpPackage( + headers: payloadModel.headers, + payload: payloadModel.body, + ); + final result = await _uploader?.upload(package: package); + return result == RequestResult.success; + } + + Future _writeToFile(String fileName, String payload) async { + final cacheDirectory = await _getCacheDirectory(); + if (!cacheDirectory.existsSync()) { + cacheDirectory.createSync(recursive: true); + } + + final file = File('${cacheDirectory.path}/$fileName'); + await file.writeAsString(payload); + } + + String _generateFileName() { + final timestamp = BugsnagClockImpl.instance.now().millisecondsSinceEpoch; + return 'payload_$timestamp.json'; + } + + Future _getCacheDirectory() async { + final appCacheDir = await getApplicationSupportDirectory(); + return Directory('${appCacheDir.path}/$_cacheDirectoryName'); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue_builder.dart new file mode 100644 index 0000000..7b2cfb7 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/retry_queue_builder.dart @@ -0,0 +1,13 @@ +import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; + +abstract class RetryQueueBuilder { + RetryQueue build(Uploader uploader); +} + +class RetryQueueBuilderImpl implements RetryQueueBuilder { + @override + RetryQueue build(Uploader uploader) { + return FileRetryQueue(uploader); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart index d0dbd0d..024fc63 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -1,10 +1,17 @@ +import 'dart:io'; import 'package:bugsnag_flutter_performance/src/uploader/sampler.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; abstract class Uploader { - Future upload({required OtlpPackage package}); + Future upload({required OtlpPackage package}); +} + +enum RequestResult { + success, + retriableFailure, + permanentFailure, } class UploaderImpl implements Uploader { @@ -22,7 +29,7 @@ class UploaderImpl implements Uploader { }); @override - Future upload({ + Future upload({ required OtlpPackage package, }) async { final sentAtTime = clock.now().toUtc(); @@ -34,10 +41,32 @@ class UploaderImpl implements Uploader { 'Bugsnag-Span-Sampling': '1:1' }; headers.addAll(package.headers); - final request = await client.post(url: url); - request.setHeaders(headers); - request.setBody(package.payload); - final response = await request.send(); - return response.statusCode / 100 == 2; + try { + final request = await client.post(url: url); + request.setHeaders(headers); + request.setBody(package.payload); + final response = await request.send(); + return _getResult(response.statusCode); + } on SocketException catch (_) { + return RequestResult.retriableFailure; + } catch (_) { + return RequestResult.permanentFailure; + } + } + + RequestResult _getResult(int statusCode) { + switch (statusCode) { + case 200: + case 202: + return RequestResult.success; + case 0: + case 408: + case 429: + return RequestResult.retriableFailure; + default: + return statusCode >= 500 + ? RequestResult.retriableFailure + : RequestResult.permanentFailure; + } } } diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index a0864cc..4150b3b 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -1,9 +1,29 @@ +import 'dart:typed_data'; + import 'package:bugsnag_flutter_performance/src/client.dart'; import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/retry_queue_builder.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:flutter_test/flutter_test.dart'; +class MockRetryQueue implements RetryQueue { + @override + Future enqueue( + {required Map headers, required Uint8List body}) async {} + @override + Future flush() async {} +} + +class MockRetryQueueBuilder implements RetryQueueBuilder { + @override + RetryQueue build(Uploader uploader) { + return MockRetryQueue(); + } +} + void main() { const apiKey = 'TestApiKey'; final endpoint = Uri.tryParse('https://bugsnag.com')!; @@ -13,6 +33,7 @@ void main() { setUp(() { client = BugsnagPerformanceClientImpl(); + client.retryQueueBuilder = MockRetryQueueBuilder(); }); group('start', () { test('should set configuration with the provided parameters', () async { From 950e7a2011b352e53c6da576863180920efb17b6 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 29 Nov 2023 09:03:30 +0100 Subject: [PATCH 18/66] fixture improvements (#18) * cleanup * typo * typo * more logs * internal config * format * setup native proxy get * add proxy to fixture generation * add clear text traffic permission to xcode fixture --- .buildkite/pipeline.yml | 1 + features/fixture_resources/lib/main.dart | 178 ++++++++++-------- .../lib/scenarios/manual_span_scenario.dart | 11 +- .../lib/scenarios/scenario.dart | 17 +- .../lib/scenarios/start_sdk_default.dart | 6 +- features/scripts/generate_fixture.sh | 8 + .../src/bugsnag_performance_public_api.dart | 6 +- .../lib/src/client.dart | 12 +- .../lib/src/configuration.dart | 8 + 9 files changed, 145 insertions(+), 102 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b0ad78a..31cd461 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -46,6 +46,7 @@ steps: artifacts#v1.5.0: download: "features/fixtures/mazerunner/build/ios/ipa/mazerunner.ipa" upload: "maze_output/failed/**/*" + upload: "maze_output/passed/**/*" docker-compose#v4.7.0: pull: maze-runner run: maze-runner diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index cd4ef58..f91a8b5 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -4,23 +4,46 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:mazerunner/channels.dart'; -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:flutter/material.dart'; +import 'package:native_flutter_proxy/custom_proxy.dart'; +import 'package:native_flutter_proxy/native_proxy_reader.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:http/http.dart' as http; import 'scenarios/scenario.dart'; import 'scenarios/scenarios.dart'; -import 'package:http/http.dart' as http; void log(String message) { print('[MazeRunner] $message'); } -void main() { +void main() async { + await setupProxy(); runApp(const MazeRunnerFlutterApp()); } +Future setupProxy() async { + WidgetsFlutterBinding.ensureInitialized(); + + bool enabled = false; + String? host; + int? port; + try { + ProxySetting settings = await NativeProxyReader.proxySetting; + enabled = settings.enabled; + host = settings.host; + port = settings.port; + } catch (e) { + print(e); + } + if (enabled && host != null) { + final proxy = CustomProxy(ipAddress: host, port: port); + proxy.enable(); + print("proxy enabled"); + } +} + + extension StringGet on Map { String? string(K key) { final value = this[key]; @@ -32,7 +55,6 @@ class FixtureConfig { static Uri MAZE_HOST = Uri.parse(""); } -/// Represents a MazeRunner command class Command { final String action; final String scenarioName; @@ -60,63 +82,67 @@ class Command { class MazeRunnerFlutterApp extends StatelessWidget { const MazeRunnerFlutterApp({Key? key}) : super(key: key); - // This widget is the root of your application. @override Widget build(BuildContext context) { + log('Building MazeRunnerFlutterApp'); return MaterialApp( title: 'Bugsnag Test', theme: ThemeData( primaryColor: const Color.fromARGB(255, 73, 73, 227), ), - home: FutureBuilder(future: Future(() async { - for (var i = 0; i < 30; i++) { - try { - final Directory directory = await appFilesDirectory(); - final File file = File( - '${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); - final text = await file.readAsString(); - print("fixture_config.json found with contents: $text"); - Map json = jsonDecode(text); - if (json.containsKey('maze_address')) { - print("fixture_config.json found with contents: $text"); - FixtureConfig.MAZE_HOST = - Uri.parse("http://" + json['maze_address']); - return json['maze_address']; - } - } catch (e) { - print("Couldn't read fixture_config.json: $e"); - } - await Future.delayed(const Duration(seconds: 1)); - } - print( - "fixture_config.json not read within 30s, defaulting to BrowserStack address"); - FixtureConfig.MAZE_HOST = Uri.parse('bs-local.com:9339'); - return 'bs-local.com:9339'; - }), builder: (_, mazerunnerUrl) { - if (mazerunnerUrl.data != null) { - return MazeRunnerHomePage( - mazerunnerUrl: mazerunnerUrl.data!, - ); - } else { - return Container( + home: FutureBuilder( + future: _getMazeRunnerUrl(), + builder: (_, mazerunnerUrl) { + if (mazerunnerUrl.data != null) { + return MazeRunnerHomePage( + mazerunnerUrl: mazerunnerUrl.data!, + ); + } else { + return Container( color: Colors.white, - child: const Center(child: CircularProgressIndicator())); - } - }), + child: const Center(child: CircularProgressIndicator()), + ); + } + }, + ), ); } - Future appFilesDirectory() async { - if (Platform.isAndroid) { - return await getExternalStorageDirectory() ?? - await getApplicationDocumentsDirectory(); + Future _getMazeRunnerUrl() async { + log('Fetching MazeRunner URL'); + for (var i = 0; i < 30; i++) { + try { + final Directory directory = await appFilesDirectory(); + final File file = File('${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); + final text = await file.readAsString(); + log("fixture_config.json found with contents: $text"); + Map json = jsonDecode(text); + if (json.containsKey('maze_address')) { + FixtureConfig.MAZE_HOST = Uri.parse('http://${json['maze_address']}'); + return FixtureConfig.MAZE_HOST.toString(); + } + } catch (e) { + log("Couldn't read fixture_config.json: $e"); + } + await Future.delayed(const Duration(seconds: 1)); } - return await getApplicationDocumentsDirectory(); + log("fixture_config.json not read within 30s, defaulting to BrowserStack address"); + FixtureConfig.MAZE_HOST = Uri.parse('http://bs-local.com:9339'); + log('using ${FixtureConfig.MAZE_HOST} as the MazeRunner URL'); + return FixtureConfig.MAZE_HOST.toString(); + } + + Future appFilesDirectory() async { + log('Fetching app files directory'); + return Platform.isAndroid + ? await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory() + : await getApplicationDocumentsDirectory(); } } class MazeRunnerHomePage extends StatefulWidget { final String mazerunnerUrl; + const MazeRunnerHomePage({ Key? key, required this.mazerunnerUrl, @@ -136,32 +162,32 @@ class _HomePageState extends State { @override void initState() { super.initState(); + log('Initializing _HomePageState'); _scenarioNameController = TextEditingController(); _extraConfigController = TextEditingController(); _commandEndpointController = TextEditingController( - text: 'http://${widget.mazerunnerUrl}/command', + text: '${widget.mazerunnerUrl}/command', ); _notifyEndpointController = TextEditingController( - text: 'http://${widget.mazerunnerUrl}/notify', + text: '${widget.mazerunnerUrl}/notify', ); _sessionEndpointController = TextEditingController( - text: 'http://${widget.mazerunnerUrl}/sessions', + text: '${widget.mazerunnerUrl}/sessions', ); _onRunCommand(context, retry: true); } @override void dispose() { + log('Disposing _HomePageState'); _scenarioNameController.dispose(); _extraConfigController.dispose(); _commandEndpointController.dispose(); _notifyEndpointController.dispose(); _sessionEndpointController.dispose(); - super.dispose(); } - /// Fetches the next command void _onRunCommand(BuildContext context, {bool retry = false}) async { log('Fetching the next command'); @@ -170,22 +196,20 @@ class _HomePageState extends State { final response = await http.get(Uri.parse(commandUrl)); if (response.statusCode == 200) { - // If the server did return a 200 OK response, - // then parse the JSON. - log('The body is:${response.body}'); + log('Received response with status code 200. Body: ${response.body}'); if (response.body.isEmpty) { log('Empty command, retrying...'); if (retry) { - Future.delayed(const Duration(seconds: 1)) - .then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); } return; } + final command = Command.fromJsonString(response.body); _scenarioNameController.text = command.scenarioName; _extraConfigController.text = command.extraConfig; - print("Doing Action: ${command.action}"); + log("Received command: Action - ${command.action}, Scenario Name - ${command.scenarioName}, Extra Config - ${command.extraConfig}"); switch (command.action) { case 'clear_cache': @@ -196,29 +220,30 @@ class _HomePageState extends State { break; } } else { + log('Received response with status code ${response.statusCode}.'); if (retry) { - Future.delayed(const Duration(seconds: 1)) - .then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); } } } Future _clearPersistentData() async { - print("Should clear the cache"); + log("Clearing the cache"); final appCacheDir = await getApplicationSupportDirectory(); try { - await Directory('${appCacheDir.path}/bugsnag-performance') - .delete(recursive: true); - print("Clear cache complete"); + await Directory('${appCacheDir.path}/bugsnag-performance').delete(recursive: true); + log("Cache cleared successfully"); } catch (e) { - print("Couldn't delete bugsnag-performance directory: $e"); + log("Couldn't delete bugsnag-performance directory: $e"); } } - /// Starts Bugsnag - Future _onStartBugsnag() async {} + Future _onStartBugsnag() async { + log("Starting Bugsnag"); + // Implementation goes here + log("Bugsnag started successfully"); + } - /// Runs a scenario, starting bugsnag first void _onRunScenario(BuildContext context) async { final scenario = _initScenario(context); if (scenario == null) { @@ -239,12 +264,10 @@ class _HomePageState extends State { await scenario.run(); } - /// Initializes a scenario Scenario? _initScenario(BuildContext context) { final name = _scenarioNameController.value.text; log('Initializing scenario: $name'); - final scenarioIndex = - scenarios.indexWhere((element) => element.name == name); + final scenarioIndex = scenarios.indexWhere((element) => element.name == name); if (scenarioIndex == -1) { log('Cannot find Scenario $name. Has it been added to scenarios.dart?'); @@ -255,7 +278,6 @@ class _HomePageState extends State { ), ), ); - return null; } @@ -264,6 +286,7 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { + log('Building _HomePageState'); return Scaffold( body: SingleChildScrollView( child: Padding( @@ -272,13 +295,14 @@ class _HomePageState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox( - height: 400.0, - width: double.infinity, - child: TextButton( - child: const Text("Run Command"), - onPressed: () => _onRunCommand(context), - key: const Key("runCommand"), - )), + height: 400.0, + width: double.infinity, + child: TextButton( + child: const Text("Run Command"), + onPressed: () => _onRunCommand(context), + key: const Key("runCommand"), + ), + ), TextField( controller: _scenarioNameController, key: const Key("scenarioName"), diff --git a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart index 0b07de2..cf96af2 100644 --- a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart @@ -1,18 +1,11 @@ -import 'dart:convert'; - import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:mazerunner/main.dart'; - import 'scenario.dart'; -import 'package:http/http.dart' as http; class ManualSpanScenario extends Scenario { @override Future run() async { - BugsnagPerformance.start( - apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); - BugsnagPerformance.setBatchSize(1); + await startBugsnag(); + setBatchSize(1); final span = BugsnagPerformance.startSpan('ManualSpanScenario'); span.end(); } diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 831cb72..1d71a26 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -3,6 +3,7 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:flutter/widgets.dart'; import '../channels.dart'; +import '../main.dart'; abstract class Scenario { String? extraConfig; @@ -12,15 +13,19 @@ abstract class Scenario { await MazeRunnerChannels.clearPersistentData(); } - // Future startBugsnag() => null; - Widget? createWidget() => null; Future run(); -} -void expect(dynamic actual, dynamic expected) { - if (actual != expected) { - throw AssertionError('Expected \'$expected\' but got \'$actual\''); + void setBatchSize(int size) { + BugsnagPerformance.setExtraConfig("autoTriggerExportOnBatchSize", size); } + + Future startBugsnag() async + { + await BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces')); + } + } diff --git a/features/fixture_resources/lib/scenarios/start_sdk_default.dart b/features/fixture_resources/lib/scenarios/start_sdk_default.dart index 3b90637..617b601 100644 --- a/features/fixture_resources/lib/scenarios/start_sdk_default.dart +++ b/features/fixture_resources/lib/scenarios/start_sdk_default.dart @@ -1,12 +1,8 @@ -import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:mazerunner/main.dart'; import 'scenario.dart'; class StartSdkDefault extends Scenario { @override Future run() async { - BugsnagPerformance.start( - apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + await startBugsnag(); } } diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index df0aa52..aa1cd0e 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -13,6 +13,8 @@ EXPORT_OPTIONS=features/fixture_resources/exportOptions.plist XCODE_PROJECT=features/fixtures/mazerunner/ios/Runner.xcodeproj/project.pbxproj +XCODE_PLIST=features/fixtures/mazerunner/ios/Runner/Info.plist + ANDROID_MANIFEST=features/fixtures/mazerunner/android/app/src/main/AndroidManifest.xml DART_LOCATION=features/fixtures/mazerunner/lib @@ -39,10 +41,16 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" path_provider $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" http +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" native_flutter_proxy + echo "Add dev team to Xcode project" sed -i '' "s/ENABLE_BITCODE = NO;/ENABLE_BITCODE = NO;\nDEVELOPMENT_TEAM = 7W9PZ27Y5F;\nCODE_SIGN_STYLE = Automatic;/g" "$XCODE_PROJECT" +echo "Add cleartext permission to xcode plist" + +sed -i '' "s/CFBundleDevelopmentRegion<\/key>/NSAppTransportSecurity<\/key>NSAllowsArbitraryLoads<\/key><\/dict>\nCFBundleDevelopmentRegion<\/key>/g" "$XCODE_PLIST" + echo "Add Android internet permission" sed -i '' "s/<\/application>/<\/application>\n/g" "$ANDROID_MANIFEST" diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 26570db..d6d2b92 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -18,11 +18,11 @@ class BugsnagPerformance { static void runApp({ FutureOr Function()? runApp, }) { - //TODO implement + //TODO implement this during auto instrumentation // _client.runApp(runApp: runApp); } - static void setBatchSize(int batchSize) { - _client.setBatchSize(batchSize); + static void setExtraConfig(String key, dynamic value) { + _client.setExtraConfig(key, value); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 163a5c1..fd12b11 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -29,6 +29,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { Sampler? _sampler; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; + final Map _initialExtraConfig = {}; BugsnagPerformanceClientImpl() { retryQueueBuilder = RetryQueueBuilderImpl(); @@ -45,6 +46,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { apiKey: apiKey, endpoint: endpoint ?? Uri.parse(_defaultEndpoint), ); + _initialExtraConfig.forEach((key, value) { + setExtraConfig(key, value); + }); _setup(); await _retryQueue?.flush(); } @@ -98,7 +102,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } } - void setBatchSize(int batchSize) { - configuration?.autoTriggerExportOnBatchSize = batchSize; + void setExtraConfig(String key, dynamic value) { + if (configuration == null) { + _initialExtraConfig[key] = value; + } else { + configuration?.applyExtraConfig(key, value); + } } } diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index f2098ac..fc0d3ed 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -3,4 +3,12 @@ class BugsnagPerformanceConfiguration { String? apiKey; Uri? endpoint; int autoTriggerExportOnBatchSize = 100; + + void applyExtraConfig(String key, dynamic value) { + switch (key) { + case 'autoTriggerExportOnBatchSize': + autoTriggerExportOnBatchSize = value; + break; + } + } } From 771899af9149a27077b218a2d3a4e4d4c444c5fd Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 29 Nov 2023 15:02:08 +0100 Subject: [PATCH 19/66] ios bit bar local support (#20) --- .gitignore | 1 + Gemfile | 2 +- Gemfile.lock | 20 ++--- features/fixture_resources/lib/main.dart | 75 +++++++++++-------- .../lib/scenarios/scenario.dart | 4 +- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index eb8de57..720e058 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ maze_output/ # ignored as fixtures are generated features/fixtures +bb.ready diff --git a/Gemfile b/Gemfile index cadf196..35982e4 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' # A reference to Maze Runner is only needed for running tests locally and if committed it must be # portable for CI, e.g. a specific release. However, leaving it commented out would mean quicker CI. -gem 'bugsnag-maze-runner', '~> 8.0' +gem 'bugsnag-maze-runner', '~> 8.13.2' # Use a specific branch #gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'master' diff --git a/Gemfile.lock b/Gemfile.lock index 4441577..364ab8c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,7 +10,7 @@ GEM selenium-webdriver (~> 4.2, < 4.6) bugsnag (6.26.0) concurrent-ruby (~> 1.0) - bugsnag-maze-runner (8.2.0) + bugsnag-maze-runner (8.13.2) appium_lib (~> 12.0.0) appium_lib_core (~> 5.4.0) bugsnag (~> 6.24) @@ -70,7 +70,7 @@ GEM faye-websocket (0.11.3) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.15.5) + ffi (1.16.3) hana (1.3.7) json_schemer (0.2.25) ecma-re-validator (~> 0.3) @@ -78,19 +78,19 @@ GEM regexp_parser (~> 2.0) simpleidn (~> 0.2) uri_template (~> 0.7) - mime-types (3.4.1) + mime-types (3.5.1) mime-types-data (~> 3.2015) - mime-types-data (3.2023.0218.1) + mime-types-data (3.2023.1003) multi_test (0.1.2) - nokogiri (1.15.3-x86_64-darwin) + nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) optimist (3.0.1) os (1.0.1) power_assert (2.0.3) - racc (1.7.1) + racc (1.7.3) rack (2.2.8) rake (12.3.3) - regexp_parser (2.8.1) + regexp_parser (2.8.2) rexml (3.2.6) rubyzip (2.3.2) selenium-webdriver (4.5.0) @@ -107,10 +107,10 @@ GEM tomlrb (2.0.3) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1) uri_template (0.7.0) webrick (1.7.0) - websocket (1.2.9) + websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -120,7 +120,7 @@ PLATFORMS x86_64-darwin-21 DEPENDENCIES - bugsnag-maze-runner (~> 8.0) + bugsnag-maze-runner (~> 8.13.2) BUNDLED WITH 2.4.8 diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index f91a8b5..10095dc 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -43,7 +43,6 @@ Future setupProxy() async { } } - extension StringGet on Map { String? string(K key) { final value = this[key]; @@ -113,7 +112,8 @@ class MazeRunnerFlutterApp extends StatelessWidget { for (var i = 0; i < 30; i++) { try { final Directory directory = await appFilesDirectory(); - final File file = File('${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); + final File file = File( + '${directory.path.replaceAll('app_flutter', 'files')}/fixture_config.json'); final text = await file.readAsString(); log("fixture_config.json found with contents: $text"); Map json = jsonDecode(text); @@ -135,7 +135,8 @@ class MazeRunnerFlutterApp extends StatelessWidget { Future appFilesDirectory() async { log('Fetching app files directory'); return Platform.isAndroid - ? await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory() + ? await getExternalStorageDirectory() ?? + await getApplicationDocumentsDirectory() : await getApplicationDocumentsDirectory(); } } @@ -190,40 +191,48 @@ class _HomePageState extends State { void _onRunCommand(BuildContext context, {bool retry = false}) async { log('Fetching the next command'); - final commandUrl = _commandEndpointController.value.text; + try { + final response = await http.get(Uri.parse(commandUrl)); + if (response.statusCode == 200) { + log('Received response with status code 200. Body: ${response.body}'); + + if (response.body.isEmpty) { + log('Empty command, retrying...'); + if (retry) { + Future.delayed(const Duration(seconds: 1)) + .then((value) => _onRunCommand(context, retry: true)); + } + return; + } - final response = await http.get(Uri.parse(commandUrl)); - - if (response.statusCode == 200) { - log('Received response with status code 200. Body: ${response.body}'); - - if (response.body.isEmpty) { - log('Empty command, retrying...'); + final command = Command.fromJsonString(response.body); + _scenarioNameController.text = command.scenarioName; + _extraConfigController.text = command.extraConfig; + log("Received command: Action - ${command.action}, Scenario Name - ${command.scenarioName}, Extra Config - ${command.extraConfig}"); + + switch (command.action) { + case 'clear_cache': + await _clearPersistentData(); + break; + case 'run_scenario': + _onRunScenario(context); + break; + } + } else { + log('Received response with status code ${response.statusCode}.'); if (retry) { - Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)) + .then((value) => _onRunCommand(context, retry: true)); } - return; - } - - final command = Command.fromJsonString(response.body); - _scenarioNameController.text = command.scenarioName; - _extraConfigController.text = command.extraConfig; - log("Received command: Action - ${command.action}, Scenario Name - ${command.scenarioName}, Extra Config - ${command.extraConfig}"); - - switch (command.action) { - case 'clear_cache': - await _clearPersistentData(); - break; - case 'run_scenario': - _onRunScenario(context); - break; } - } else { - log('Received response with status code ${response.statusCode}.'); + } catch (e) { + log('Error fetching command: $e \nRetrying...'); if (retry) { - Future.delayed(const Duration(seconds: 1)).then((value) => _onRunCommand(context, retry: true)); + Future.delayed(const Duration(seconds: 1)) + .then((value) => _onRunCommand(context, retry: true)); } + return; } } @@ -231,7 +240,8 @@ class _HomePageState extends State { log("Clearing the cache"); final appCacheDir = await getApplicationSupportDirectory(); try { - await Directory('${appCacheDir.path}/bugsnag-performance').delete(recursive: true); + await Directory('${appCacheDir.path}/bugsnag-performance') + .delete(recursive: true); log("Cache cleared successfully"); } catch (e) { log("Couldn't delete bugsnag-performance directory: $e"); @@ -267,7 +277,8 @@ class _HomePageState extends State { Scenario? _initScenario(BuildContext context) { final name = _scenarioNameController.value.text; log('Initializing scenario: $name'); - final scenarioIndex = scenarios.indexWhere((element) => element.name == name); + final scenarioIndex = + scenarios.indexWhere((element) => element.name == name); if (scenarioIndex == -1) { log('Cannot find Scenario $name. Has it been added to scenarios.dart?'); diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 1d71a26..cfd8ed1 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -21,11 +21,9 @@ abstract class Scenario { BugsnagPerformance.setExtraConfig("autoTriggerExportOnBatchSize", size); } - Future startBugsnag() async - { + Future startBugsnag() async { await BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces')); } - } From 82c852335d93159d9e01405ea350dd78178eb067 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:27:50 +0100 Subject: [PATCH 20/66] [PLAT-11229] Server controlled p-value update and persistence of value (#21) * [PLAT-11229] Server controlled p-value update and persistence of value [PLAT-11230] Initial p-value request * Fixed E2E tests * Fixed E2E tests --------- Co-authored-by: Robert --- features/fixture_resources/lib/main.dart | 12 +++ .../lib/scenarios/initial_p_scenario.dart | 35 +++++++ .../probability_expiry_scenario.dart | 22 +++++ .../lib/scenarios/scenario.dart | 2 + .../lib/scenarios/scenarios.dart | 6 +- features/initial-p-value.feature | 45 +++++++++ features/steps/flutter_steps.rb | 13 +++ .../lib/src/client.dart | 56 +++++++++++- .../lib/src/configuration.dart | 8 ++ .../lib/src/uploader/client_response.dart | 4 + .../lib/src/uploader/package_builder.dart | 15 +++ .../lib/src/uploader/sampler.dart | 75 +++++++++++++-- .../uploader/sampling_probability_store.dart | 91 +++++++++++++++++++ .../lib/src/uploader/uploader.dart | 2 +- 14 files changed, 373 insertions(+), 13 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/initial_p_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart create mode 100644 features/initial-p-value.feature create mode 100644 packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index 10095dc..805c1ec 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -58,11 +58,13 @@ class Command { final String action; final String scenarioName; final String extraConfig; + final List args; const Command({ required this.action, required this.scenarioName, required this.extraConfig, + required this.args, }); factory Command.fromJsonString(String jsonString) { @@ -74,6 +76,7 @@ class Command { action: map.string('action')!, scenarioName: map.string('scenario_name') ?? '', extraConfig: map.string('extra_config') ?? '', + args: map['args'] ?? [], ); } } @@ -159,6 +162,7 @@ class _HomePageState extends State { late TextEditingController _commandEndpointController; late TextEditingController _notifyEndpointController; late TextEditingController _sessionEndpointController; + Scenario? _currentScenario; @override void initState() { @@ -218,6 +222,9 @@ class _HomePageState extends State { case 'run_scenario': _onRunScenario(context); break; + case 'invoke_method': + _onInvokeMethod(command.args[0]); + break; } } else { log('Received response with status code ${response.statusCode}.'); @@ -271,6 +278,7 @@ class _HomePageState extends State { } log('Running scenario'); + _currentScenario = scenario; await scenario.run(); } @@ -295,6 +303,10 @@ class _HomePageState extends State { return scenarios[scenarioIndex].init(); } + void _onInvokeMethod(String name) { + _currentScenario?.invokeMethod(name); + } + @override Widget build(BuildContext context) { log('Building _HomePageState'); diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart new file mode 100644 index 0000000..95b9b8f --- /dev/null +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; +import 'package:http/http.dart' as http; + +class InitialPScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 2000); + BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 2000); + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + setBatchSize(1); + BugsnagPerformance.startSpan('First').end(); + } + + void step2() { + BugsnagPerformance.startSpan('Second').end(); + } + + @override + void invokeMethod(String name) { + switch (name) { + case 'step2': + step2(); + break; + default: + break; + } + } +} diff --git a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart new file mode 100644 index 0000000..ab5f260 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; +import 'package:http/http.dart' as http; + +class ProbabilityExpiryScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 100); + BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 100); + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + setBatchSize(1); + await Future.delayed(Duration(milliseconds: 500)); + final span = BugsnagPerformance.startSpan('myspan'); + span.end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index cfd8ed1..6399d1e 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -26,4 +26,6 @@ abstract class Scenario { apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces')); } + + void invokeMethod(String name) {} } diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 335a916..150605f 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,4 +1,6 @@ +import 'initial_p_scenario.dart'; import 'manual_span_scenario.dart'; +import 'probability_expiry_scenario.dart'; import 'start_sdk_default.dart'; import 'scenario.dart'; @@ -11,5 +13,7 @@ class ScenarioInfo { // Flutter obfuscation *requires* that we specify the name as a raw String in order to match the runtime class final List> scenarios = [ ScenarioInfo('ManualSpanScenario', () => ManualSpanScenario()), - ScenarioInfo('StartSdkDefault', () => StartSdkDefault()) + ScenarioInfo('StartSdkDefault', () => StartSdkDefault()), + ScenarioInfo('InitialPScenario', () => InitialPScenario()), + ScenarioInfo('ProbabilityExpiryScenario', () => ProbabilityExpiryScenario()), ]; diff --git a/features/initial-p-value.feature b/features/initial-p-value.feature new file mode 100644 index 0000000..769fd39 --- /dev/null +++ b/features/initial-p-value.feature @@ -0,0 +1,45 @@ +Feature: Initial P values + + Scenario: Initial P value of 0 + Given I set the sampling probability for the next traces to "0" + And I run "InitialPScenario" + And I wait to receive a sampling request + * the sampling request "Bugsnag-Span-Sampling" header equals "1:0" + * the sampling request "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the sampling request payload field "resourceSpans" is an array with 0 elements + Then I invoke "step2" + And I should receive no traces + + Scenario: Initial P value of 1 + Given I set the sampling probability for the next traces to "1" + And I run "InitialPScenario" + And I wait to receive a sampling request + + Then the sampling request "Bugsnag-Span-Sampling" header equals "1:0" + * the sampling request "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + + Then I wait for 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "First" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + Then I discard the oldest trace + And I invoke "step2" + And I wait for 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "Second" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + + Scenario: ProbabilityExpiryScenario + Given I run "ProbabilityExpiryScenario" + And I wait to receive at least 1 sampling request + * the sampling request "Bugsnag-Span-Sampling" header equals "1:0" + * the sampling request "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the sampling request payload field "resourceSpans" is an array with 0 elements diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index 9ed6d13..9e1f8d3 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -153,3 +153,16 @@ def execute_command(action, scenario_name) Maze.check.true(device_ids.uniq.length == 1) end +When('I invoke {string}') do |method_name| + Maze::Server.commands.add({ action: "invoke_method", args: [method_name] }) + # Ensure fixture has read the command + touch_action = Appium::TouchAction.new + touch_action.tap({:x => 200, :y => 200}) + touch_action.perform + + $extra_config = '' + # Ensure fixture has read the command + count = 100 + sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1 + raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty? +end diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index fd12b11..5d75d2c 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; @@ -5,6 +6,7 @@ import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/sampler.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/sampling_probability_store.dart'; import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; @@ -27,9 +29,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { SpanBatch? _currentBatch; RetryQueue? _retryQueue; Sampler? _sampler; + DateTime? _lastSamplingProbabilityRefreshDate; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; final Map _initialExtraConfig = {}; + late final SamplingProbabilityStore _probabilityStore; BugsnagPerformanceClientImpl() { retryQueueBuilder = RetryQueueBuilderImpl(); @@ -38,6 +42,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { attributesProvider: ResourceAttributesProviderImpl(), ); _clock = BugsnagClockImpl.instance; + _probabilityStore = SamplingProbabilityStoreImpl(_clock); } @override @@ -58,8 +63,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final span = BugsnagPerformanceSpanImpl( name: name, startTime: startTime ?? _clock.now(), - onEnded: (endedSpan) { - if (_sampler?.sample(endedSpan) ?? true) { + onEnded: (endedSpan) async { + await _updateSamplingProbabilityIfNeeded(); + if (await _sampler?.sample(endedSpan) ?? true) { _currentBatch?.add(endedSpan); } }, @@ -74,7 +80,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } void _setup() { - _sampler = SamplerImpl(); + _sampler = SamplerImpl( + configuration: configuration!, + probabilityStore: _probabilityStore, + clock: _clock, + ); if (configuration?.endpoint != null && configuration?.apiKey != null) { _uploader = UploaderImpl( apiKey: configuration!.apiKey!, @@ -85,12 +95,18 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { ); _retryQueue = retryQueueBuilder.build(_uploader!); } + Timer.periodic( + Duration(milliseconds: configuration!.probabilityRequestsPause), + (timer) { + _updateSamplingProbabilityIfNeeded(force: true); + }); } void _sendBatch(SpanBatch batch) async { + await _updateSamplingProbabilityIfNeeded(); var spans = batch.drain(); if (_sampler != null) { - spans = _sampler!.sampled(spans); + spans = await _sampler!.sampled(spans); } if (spans.isEmpty) { return; @@ -102,6 +118,38 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } } + Future _updateSamplingProbabilityIfNeeded({ + bool force = false, + }) async { + if (await _sampler?.hasValidSamplingProbabilityValue() ?? true) { + return; + } + if (!_canSendSamplingProbabilityRequest() && !force) { + return; + } + await _sendSamplingProbabilityRequest(); + } + + bool _canSendSamplingProbabilityRequest() { + if (_lastSamplingProbabilityRefreshDate == null) { + return true; + } + if (configuration == null) { + return false; + } + return configuration!.probabilityRequestsPause >= + _clock + .now() + .difference(_lastSamplingProbabilityRefreshDate!) + .inMilliseconds; + } + + Future _sendSamplingProbabilityRequest() async { + _lastSamplingProbabilityRefreshDate = _clock.now(); + final package = await _packageBuilder.buildEmptyPackage(); + await _uploader?.upload(package: package); + } + void setExtraConfig(String key, dynamic value) { if (configuration == null) { _initialExtraConfig[key] = value; diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index fc0d3ed..67f39a1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -3,12 +3,20 @@ class BugsnagPerformanceConfiguration { String? apiKey; Uri? endpoint; int autoTriggerExportOnBatchSize = 100; + int probabilityRequestsPause = 30000; + int probabilityValueExpireTime = 24 * 3600 * 1000; void applyExtraConfig(String key, dynamic value) { switch (key) { case 'autoTriggerExportOnBatchSize': autoTriggerExportOnBatchSize = value; break; + case 'probabilityRequestsPause': + probabilityRequestsPause = value; + break; + case 'probabilityValueExpireTime': + probabilityValueExpireTime = value; + break; } } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart index 4a11931..9b4863a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/client_response.dart @@ -2,6 +2,7 @@ import 'dart:io'; abstract class ClientResponse { int get statusCode; + HttpHeaders get headers; } class ClientResponseImpl implements ClientResponse { @@ -12,4 +13,7 @@ class ClientResponseImpl implements ClientResponse { @override int get statusCode => response.statusCode; + + @override + HttpHeaders get headers => response.headers; } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index cb12ef0..c96b5f1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -12,6 +12,7 @@ abstract class PackageBuilder { Future build( List spans, ); + Future buildEmptyPackage(); } class PackageBuilderImpl implements PackageBuilder { @@ -41,6 +42,20 @@ class PackageBuilderImpl implements PackageBuilder { ); } + @override + Future buildEmptyPackage() async { + final payload = utf8.encode(jsonEncode({'resourceSpans': []})); + final headers = { + 'Content-Type': 'application/json', + 'Bugsnag-Integrity': _integrityDigestForData(payload: payload), + 'Bugsnag-Span-Sampling': '1:0', + }; + return OtlpPackage( + headers: headers, + payload: Uint8List.fromList(payload), + ); + } + Future> _buildPayload({ required List spans, }) async { diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart index bae8d16..62a17fe 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart @@ -1,22 +1,58 @@ +import 'dart:io'; + +import 'package:bugsnag_flutter_performance/src/configuration.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/uploader/sampling_probability_store.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; abstract class Sampler { - bool sample(BugsnagPerformanceSpan span); - List sampled(List spans); + Future hasValidSamplingProbabilityValue(); + Future sample(BugsnagPerformanceSpan span); + Future> sampled( + List spans); + void handleResponseHeaders(HttpHeaders headers); } class SamplerImpl implements Sampler { - double samplingProbability; + BugsnagPerformanceConfiguration configuration; + SamplingProbabilityStore probabilityStore; + BugsnagClock clock; static final BigInt baselineInt = BigInt.parse('18446744073709551', radix: 10); SamplerImpl({ - this.samplingProbability = 1.0, + required this.configuration, + required this.probabilityStore, + required this.clock, }); @override - bool sample(BugsnagPerformanceSpan span) { + Future hasValidSamplingProbabilityValue() async { + return await probabilityStore.samplingProbability != null; + } + + @override + Future sample(BugsnagPerformanceSpan span) async { + final samplingProbability = + await probabilityStore.samplingProbability ?? 1.0; + return _sample(span, samplingProbability); + } + + @override + Future> sampled( + List spans) async { + final samplingProbability = + await probabilityStore.samplingProbability ?? 1.0; + return spans + .where((element) => _sample( + element, + samplingProbability, + )) + .toList(); + } + + bool _sample(BugsnagPerformanceSpan span, double samplingProbability) { var isSampled = false; if (samplingProbability == 1.0) { isSampled = true; @@ -32,7 +68,32 @@ class SamplerImpl implements Sampler { } @override - List sampled(List spans) { - return spans.where((element) => sample(element)).toList(); + void handleResponseHeaders(HttpHeaders headers) async { + final samplingProbabilityHeader = + headers.value('Bugsnag-Sampling-Probability'); + if (samplingProbabilityHeader == null) { + return; + } + final headerComponents = samplingProbabilityHeader.split(';'); + final probability = double.tryParse(headerComponents.firstOrNull ?? ''); + if (probability == null || probability > 1 || probability < 0) { + return; + } + var duration = configuration.probabilityValueExpireTime / 1000; + if (headerComponents.length > 1) { + final durationFromHeader = double.tryParse(headerComponents + .firstWhere( + (element) => element.contains('duration='), + orElse: () => '', + ) + .split('=') + .lastOrNull ?? + ''); + if (durationFromHeader != null && durationFromHeader < duration) { + duration = durationFromHeader; + } + } + probabilityStore.store(probability, + clock.now().add(Duration(milliseconds: (duration * 1000).toInt()))); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart new file mode 100644 index 0000000..f25ef39 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:path_provider/path_provider.dart'; + +abstract class SamplingProbabilityStore { + Future get samplingProbability; + void store( + double samplingProbability, + DateTime expireDate, + ); +} + +class SamplingProbabilityStoreImpl implements SamplingProbabilityStore { + static const String _cacheDirectoryName = 'bugsnag-performance/v1/sampling'; + static const String _fileName = 'samplingProbability.json'; + + final BugsnagClock clock; + + double? _samplingProbability; + DateTime? _expireDate; + var _isInitialized = false; + + SamplingProbabilityStoreImpl(this.clock); + + @override + Future get samplingProbability async { + await _initializeIfNeeded(); + if (_expireDate != null && _expireDate!.isBefore(clock.now())) { + return null; + } + return _samplingProbability; + } + + @override + void store( + double samplingProbability, + DateTime expireDate, + ) async { + await _initializeIfNeeded(); + if (_samplingProbability != null && + _samplingProbability! < samplingProbability) { + return; + } + _samplingProbability = samplingProbability; + _expireDate = expireDate; + _writeToFile(samplingProbability, expireDate); + } + + Future _initializeIfNeeded() async { + if (_isInitialized) { + return; + } + final file = await _getFile(); + if (file.existsSync()) { + final content = jsonDecode(await file.readAsString()); + final expireDate = DateTime.tryParse(content['expireDate']); + if (expireDate != null && expireDate.isAfter(clock.now())) { + _samplingProbability = double.tryParse(content['value']); + _expireDate = expireDate; + } + } + + _isInitialized = true; + } + + Future _writeToFile( + double samplingProbability, + DateTime expireDate, + ) async { + final file = await _getFile(); + await file.writeAsString(jsonEncode({ + 'value': samplingProbability.toString(), + 'expireDate': expireDate.toIso8601String(), + })); + } + + Future _getFile() async { + final cacheDirectory = await _getCacheDirectory(); + if (!cacheDirectory.existsSync()) { + cacheDirectory.createSync(recursive: true); + } + return File('${cacheDirectory.path}/$_fileName'); + } + + Future _getCacheDirectory() async { + final appCacheDir = await getApplicationSupportDirectory(); + return Directory('${appCacheDir.path}/$_cacheDirectoryName'); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart index 024fc63..e84c29a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/uploader.dart @@ -38,7 +38,6 @@ class UploaderImpl implements Uploader { 'Bugsnag-Sent-At': sentAtTime .subtract(Duration(microseconds: sentAtTime.microsecond)) .toIso8601String(), - 'Bugsnag-Span-Sampling': '1:1' }; headers.addAll(package.headers); try { @@ -46,6 +45,7 @@ class UploaderImpl implements Uploader { request.setHeaders(headers); request.setBody(package.payload); final response = await request.send(); + sampler.handleResponseHeaders(response.headers); return _getResult(response.statusCode); } on SocketException catch (_) { return RequestResult.retriableFailure; From b5c50906686eb61d7babe21575e1ea412e00268b Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 4 Dec 2023 17:18:29 +0100 Subject: [PATCH 21/66] PLAT-11265 nested spans (#22) --- .../lib/scenarios/make_current_context.dart | 19 +++++++ .../new_zone_new_context_scenario.dart | 24 ++++++++ .../lib/scenarios/pass_context_scenario.dart | 21 +++++++ .../lib/scenarios/scenarios.dart | 9 +++ .../simple_nested_span_scenario.dart | 14 +++++ features/nested_spans.feature | 47 +++++++++++++++ features/steps/flutter_steps.rb | 27 +++++++++ .../src/bugsnag_performance_public_api.dart | 14 ++++- .../lib/src/client.dart | 57 +++++++++++++++---- .../lib/src/span.dart | 30 ++++++---- .../lib/src/span_context.dart | 35 ++++++++++++ 11 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/make_current_context.dart create mode 100644 features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/pass_context_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart create mode 100644 features/nested_spans.feature create mode 100644 packages/bugsnag_flutter_performance/lib/src/span_context.dart diff --git a/features/fixture_resources/lib/scenarios/make_current_context.dart b/features/fixture_resources/lib/scenarios/make_current_context.dart new file mode 100644 index 0000000..4b4c759 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/make_current_context.dart @@ -0,0 +1,19 @@ +import 'dart:async'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class MakeCurrentContextScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(3); + final span1 = BugsnagPerformance.startSpan('span1'); + final span2 = + BugsnagPerformance.startSpan('span2', makeCurrentContext: false); + final span3 = BugsnagPerformance.startSpan('span3'); + span3.end(); + span2.end(); + span1.end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart new file mode 100644 index 0000000..28f6e74 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class NewZoneNewContextScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(4); + final span1 = BugsnagPerformance.startSpan('span1'); + + runZoned(() { + final span3 = BugsnagPerformance.startSpan('span3'); + final span4 = BugsnagPerformance.startSpan('span4'); + span4.end(); + span3.end(); + }, zoneValues: {}); + + final span2 = BugsnagPerformance.startSpan('span2'); + span2.end(); + span1.end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart new file mode 100644 index 0000000..92f73ac --- /dev/null +++ b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class PassContextToNewZoneScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(3); + final span1 = BugsnagPerformance.startSpan('span1'); + runZoned(() { + final span2 = BugsnagPerformance.startSpan('span2', parentContext: span1); + span2.end(); + final span3 = BugsnagPerformance.startSpan('span3'); + span3.end(); + }, zoneValues: {}); + await Future.delayed(const Duration(milliseconds: 1000)); + span1.end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 150605f..95bca7d 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -2,6 +2,10 @@ import 'initial_p_scenario.dart'; import 'manual_span_scenario.dart'; import 'probability_expiry_scenario.dart'; import 'start_sdk_default.dart'; +import 'simple_nested_span_scenario.dart'; +import 'new_zone_new_context_scenario.dart'; +import 'pass_context_scenario.dart'; +import 'make_current_context.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -16,4 +20,9 @@ final List> scenarios = [ ScenarioInfo('StartSdkDefault', () => StartSdkDefault()), ScenarioInfo('InitialPScenario', () => InitialPScenario()), ScenarioInfo('ProbabilityExpiryScenario', () => ProbabilityExpiryScenario()), + ScenarioInfo('SimpleNestedSpanScenario', () => SimpleNestedSpanScenario()), + ScenarioInfo('NewZoneNewContextScenario', () => NewZoneNewContextScenario()), + ScenarioInfo( + 'PassContextToNewZoneScenario', () => PassContextToNewZoneScenario()), + ScenarioInfo('MakeCurrentContextScenario', () => MakeCurrentContextScenario()) ]; diff --git a/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart new file mode 100644 index 0000000..d962dc5 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart @@ -0,0 +1,14 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class SimpleNestedSpanScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(2); + final span1 = BugsnagPerformance.startSpan('span1'); + final span2 = BugsnagPerformance.startSpan('span2'); + span2.end(); + span1.end(); + } +} diff --git a/features/nested_spans.feature b/features/nested_spans.feature new file mode 100644 index 0000000..094b979 --- /dev/null +++ b/features/nested_spans.feature @@ -0,0 +1,47 @@ +Feature: Nested Spans + + Scenario: Simple Nested Span + When I run "SimpleNestedSpanScenario" + * I wait for 2 spans + * the span named "span1" exists + * the span named "span2" exists + * the span named "span1" is the parent of the span named "span2" + * the span named "span1" has no parent + + Scenario: New Zone New Context + When I run "NewZoneNewContextScenario" + * I wait for 4 spans + * the span named "span1" exists + * the span named "span2" exists + * the span named "span3" exists + * the span named "span4" exists + + * the span named "span1" has no parent + * the span named "span3" has no parent + + * the span named "span1" is the parent of the span named "span2" + * the span named "span3" is the parent of the span named "span4" + + Scenario: Pass Context To New Zone + When I run "PassContextToNewZoneScenario" + * I wait for 3 spans + * the span named "span1" exists + * the span named "span2" exists + * the span named "span3" exists + + * the span named "span1" has no parent + + * the span named "span1" is the parent of the span named "span2" + * the span named "span1" is the parent of the span named "span3" + + Scenario: Make Current Context False + When I run "MakeCurrentContextScenario" + * I wait for 3 spans + * the span named "span1" exists + * the span named "span2" exists + * the span named "span3" exists + + * the span named "span1" has no parent + + * the span named "span1" is the parent of the span named "span2" + * the span named "span1" is the parent of the span named "span3" diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index 9e1f8d3..4a9026a 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -166,3 +166,30 @@ def execute_command(action, scenario_name) sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1 raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty? end +Then('the span named {string} exists') do |span_name| + spans = spans_from_request_list(Maze::Server.list_for("traces")) + + spans_with_name = spans.find_all { |span| span['name'].eql?(span_name) } + + Maze.check.true(spans_with_name.length() == 1); +end + +Then('the span named {string} is the parent of the span named {string}') do |span1name, span2name| + + spans = spans_from_request_list(Maze::Server.list_for("traces")) + + span1 = spans.find_all { |span| span['name'].eql?(span1name) }.first + + span2 = spans.find_all { |span| span['name'].eql?(span2name) }.first + + Maze.check.true(span1['spanId'] == span2['parentSpanId']); + +end + +Then('the span named {string} has no parent') do |spanName| + spans = spans_from_request_list(Maze::Server.list_for("traces")) + + span1 = spans.find_all { |span| span['name'].eql?(spanName) }.first + + Maze.check.true(span1['parentSpanId'] == nil); +end diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index d6d2b92..9c5ea50 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:bugsnag_flutter_performance/src/span_context.dart'; + import '../bugsnag_flutter_performance.dart'; import 'client.dart'; @@ -11,8 +13,16 @@ class BugsnagPerformance { return _client.start(apiKey: apiKey, endpoint: endpoint); } - static BugsnagPerformanceSpan startSpan(String name) { - return _client.startSpan(name); + static BugsnagPerformanceSpan startSpan(String name, + {DateTime? startTime, + BugsnagPerformanceSpanContext? parentContext, + bool? makeCurrentContext = true}) { + return _client.startSpan( + name, + startTime: startTime, + parentContext: parentContext, + makeCurrentContext: makeCurrentContext, + ); } static void runApp({ diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 5d75d2c..926ed40 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; +import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue_builder.dart'; @@ -34,6 +35,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { late final BugsnagClock _clock; final Map _initialExtraConfig = {}; late final SamplingProbabilityStore _probabilityStore; + final Map _zoneContextStacks = {}; BugsnagPerformanceClientImpl() { retryQueueBuilder = RetryQueueBuilderImpl(); @@ -59,23 +61,37 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } @override - BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}) { + BugsnagPerformanceSpan startSpan(String name, + {DateTime? startTime, + BugsnagPerformanceSpanContext? parentContext, + bool? makeCurrentContext = true}) { + if (parentContext != null) { + _addContext(parentContext); + } + + final parent = parentContext ?? _getCurrentContext(); + final span = BugsnagPerformanceSpanImpl( - name: name, - startTime: startTime ?? _clock.now(), - onEnded: (endedSpan) async { - await _updateSamplingProbabilityIfNeeded(); - if (await _sampler?.sample(endedSpan) ?? true) { - _currentBatch?.add(endedSpan); - } - }, - ); + name: name, + startTime: startTime ?? _clock.now(), + onEnded: (endedSpan) async { + await _updateSamplingProbabilityIfNeeded(); + if (await _sampler?.sample(endedSpan) ?? true) { + _currentBatch?.add(endedSpan); + } + }, + parentSpanId: parent?.spanId); span.clock = _clock; if (configuration != null) { _currentBatch ??= SpanBatchImpl(); _currentBatch?.configure(configuration!); _currentBatch?.onBatchFull = _sendBatch; } + + if (makeCurrentContext == true) { + _addContext(span); + } + return span; } @@ -157,4 +173,25 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { configuration?.applyExtraConfig(key, value); } } + + BugsnagPerformanceSpanContextStack? _getContextStack() { + if (_zoneContextStacks.containsKey(Zone.current.hashCode)) { + return _zoneContextStacks[Zone.current.hashCode]; + } else { + _zoneContextStacks[Zone.current.hashCode] = + BugsnagPerformanceSpanContextStackImpl(); + return _zoneContextStacks[Zone.current.hashCode]; + } + } + + void _addContext(BugsnagPerformanceSpanContext newContext) { + var stack = _getContextStack(); + if (stack?.getCurrentContext() != newContext) { + _getContextStack()?.pushContext(newContext); + } + } + + BugsnagPerformanceSpanContext? _getCurrentContext() { + return _getContextStack()?.getCurrentContext(); + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 0ffd104..2cf6e30 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -1,30 +1,33 @@ import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; +import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:bugsnag_flutter_performance/src/util/random.dart'; typedef TraceId = BigInt; typedef SpanId = BigInt; -abstract class BugsnagPerformanceSpan { +abstract class BugsnagPerformanceSpan implements BugsnagPerformanceSpanContext { + @override TraceId get traceId; + @override SpanId get spanId; SpanId? parentSpanId; void end(); dynamic toJson(); } -class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { - BugsnagPerformanceSpanImpl({ - required this.name, - required this.startTime, - void Function(BugsnagPerformanceSpan)? onEnded, - TraceId? traceId, - SpanId? spanId, - BugsnagPerformanceSpanAttributes? attributes, - this.parentSpanId, - }) { +class BugsnagPerformanceSpanImpl + implements BugsnagPerformanceSpan, BugsnagPerformanceSpanContext { + BugsnagPerformanceSpanImpl( + {required this.name, + required this.startTime, + void Function(BugsnagPerformanceSpan)? onEnded, + TraceId? traceId, + SpanId? spanId, + this.parentSpanId, + BugsnagPerformanceSpanAttributes? attributes}) { this.traceId = traceId ?? randomTraceId(); this.spanId = spanId ?? randomSpanId(); this.onEnded = onEnded ?? _onEnded; @@ -95,6 +98,11 @@ class BugsnagPerformanceSpanImpl implements BugsnagPerformanceSpan { attributes.samplingProbability = samplingProbability; } } + + @override + bool isOpen() { + return endTime == null; + } } String _encodeSpanId(SpanId spanId) { diff --git a/packages/bugsnag_flutter_performance/lib/src/span_context.dart b/packages/bugsnag_flutter_performance/lib/src/span_context.dart new file mode 100644 index 0000000..5fcbe9d --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/span_context.dart @@ -0,0 +1,35 @@ +import 'package:bugsnag_flutter_performance/src/span.dart'; + +abstract class BugsnagPerformanceSpanContextStack { + BugsnagPerformanceSpanContext? getCurrentContext(); + void pushContext(BugsnagPerformanceSpanContext context); +} + +class BugsnagPerformanceSpanContextStackImpl + implements BugsnagPerformanceSpanContextStack { + final List _stack = []; + + @override + BugsnagPerformanceSpanContext? getCurrentContext() { + _clearClosedSpans(); + if (_stack.isEmpty) { + return null; + } + return _stack.last; + } + + @override + void pushContext(BugsnagPerformanceSpanContext context) { + _stack.add(context); + } + + void _clearClosedSpans() { + _stack.removeWhere((span) => !span.isOpen()); + } +} + +abstract class BugsnagPerformanceSpanContext { + TraceId get traceId; + SpanId get spanId; + bool isOpen(); +} From f2f5af9295a8b5e0184643b53df344f693769266 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:40:35 +0100 Subject: [PATCH 22/66] [PLAT-11331] App Start instrumentation (#23) * [PLAT-11331] App Start instrumentation * Fixed E2E tests * Moved configuration checks to app start instrumentation * Small change in E2E tests * Small change to E2E tests * Small fixes for sampling initialization * Fixes for E2E Tests --------- Co-authored-by: Robert --- features/automatic_spans.feature | 25 +++++ .../auto_instrument_app_starts_scenario.dart | 17 ++++ .../lib/scenarios/initial_p_scenario.dart | 5 +- .../probability_expiry_scenario.dart | 1 + .../lib/scenarios/scenario.dart | 1 + .../lib/scenarios/scenarios.dart | 6 +- .../src/bugsnag_performance_public_api.dart | 9 +- .../lib/src/client.dart | 57 ++++++++--- .../lib/src/configuration.dart | 4 + .../app_start_instrumentation.dart | 98 +++++++++++++++++++ .../lib/src/span_attributes.dart | 16 ++- .../lib/src/uploader/sampler.dart | 2 +- .../uploader/sampling_probability_store.dart | 4 +- 13 files changed, 219 insertions(+), 26 deletions(-) create mode 100644 features/automatic_spans.feature create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart diff --git a/features/automatic_spans.feature b/features/automatic_spans.feature new file mode 100644 index 0000000..d40eaef --- /dev/null +++ b/features/automatic_spans.feature @@ -0,0 +1,25 @@ +Feature: Automatic instrumentation spans + + Scenario: AutoInstrumentAppStartsScenario + Given I run "AutoInstrumentAppStartsScenario" + And I wait for 4 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:4" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[AppStart/FlutterInit]" + * a span field "name" equals "[AppStartPhase/pre runApp()]" + * a span field "name" equals "[AppStartPhase/runApp()]" + * a span field "name" equals "[AppStartPhase/UI init]" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.phase" equals "pre runApp()" + * a span string attribute "bugsnag.phase" equals "runApp()" + * a span string attribute "bugsnag.phase" equals "UI init" + * a span string attribute "bugsnag.span.category" equals "app_start" + * a span string attribute "bugsnag.span.category" equals "app_start_phase" + * every span bool attribute "bugsnag.span.first_class" does not exist + * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/pre runApp()]" + * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/runApp()]" + * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/UI init]" diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart new file mode 100644 index 0000000..330f08c --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart @@ -0,0 +1,17 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentAppStartsScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", true); + BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 1000); + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + BugsnagPerformance.measureRunApp(() async => await Duration(seconds: 1)); + setBatchSize(4); + } +} diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart index 95b9b8f..0cbf299 100644 --- a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -9,8 +9,9 @@ import 'package:http/http.dart' as http; class InitialPScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 2000); - BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 2000); + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 1000); + BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 25000); BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); diff --git a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart index ab5f260..32704ce 100644 --- a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart +++ b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart @@ -9,6 +9,7 @@ import 'package:http/http.dart' as http; class ProbabilityExpiryScenario extends Scenario { @override Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 100); BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 100); BugsnagPerformance.start( diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 6399d1e..8043ebf 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -22,6 +22,7 @@ abstract class Scenario { } Future startBugsnag() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); await BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces')); diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 95bca7d..46628d2 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,3 +1,4 @@ +import 'auto_instrument_app_starts_scenario.dart'; import 'initial_p_scenario.dart'; import 'manual_span_scenario.dart'; import 'probability_expiry_scenario.dart'; @@ -24,5 +25,8 @@ final List> scenarios = [ ScenarioInfo('NewZoneNewContextScenario', () => NewZoneNewContextScenario()), ScenarioInfo( 'PassContextToNewZoneScenario', () => PassContextToNewZoneScenario()), - ScenarioInfo('MakeCurrentContextScenario', () => MakeCurrentContextScenario()) + ScenarioInfo( + 'MakeCurrentContextScenario', () => MakeCurrentContextScenario()), + ScenarioInfo('AutoInstrumentAppStartsScenario', + () => AutoInstrumentAppStartsScenario()) ]; diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 9c5ea50..fba02a8 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -25,11 +25,10 @@ class BugsnagPerformance { ); } - static void runApp({ - FutureOr Function()? runApp, - }) { - //TODO implement this during auto instrumentation - // _client.runApp(runApp: runApp); + static Future measureRunApp( + FutureOr Function() runApp, + ) async { + await _client.measureRunApp(runApp); } static void setExtraConfig(String key, dynamic value) { diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 926ed40..fba286a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/app_start_instrumentation.dart'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; @@ -20,7 +22,14 @@ const _defaultEndpoint = 'https://otlp.bugsnag.com/v1/traces'; abstract class BugsnagPerformanceClient { Future start({String? apiKey, Uri? endpoint}); - BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime}); + Future measureRunApp(FutureOr Function() runApp); + BugsnagPerformanceSpan startSpan( + String name, { + DateTime? startTime, + BugsnagPerformanceSpanContext? parentContext, + bool? makeCurrentContext = true, + BugsnagPerformanceSpanAttributes? attributes, + }); } class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { @@ -35,6 +44,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { late final BugsnagClock _clock; final Map _initialExtraConfig = {}; late final SamplingProbabilityStore _probabilityStore; + late final AppStartInstrumentation _appStartInstrumentation; final Map _zoneContextStacks = {}; BugsnagPerformanceClientImpl() { @@ -45,6 +55,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { ); _clock = BugsnagClockImpl.instance; _probabilityStore = SamplingProbabilityStoreImpl(_clock); + _appStartInstrumentation = AppStartInstrumentationImpl(client: this); } @override @@ -56,15 +67,21 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _initialExtraConfig.forEach((key, value) { setExtraConfig(key, value); }); + _appStartInstrumentation + .setEnabled(configuration?.instrumentAppStart ?? false); _setup(); + _appStartInstrumentation.didStartBugsnagPerformance(); await _retryQueue?.flush(); } @override - BugsnagPerformanceSpan startSpan(String name, - {DateTime? startTime, - BugsnagPerformanceSpanContext? parentContext, - bool? makeCurrentContext = true}) { + BugsnagPerformanceSpan startSpan( + String name, { + DateTime? startTime, + BugsnagPerformanceSpanContext? parentContext, + bool? makeCurrentContext = true, + BugsnagPerformanceSpanAttributes? attributes, + }) { if (parentContext != null) { _addContext(parentContext); } @@ -72,15 +89,17 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final parent = parentContext ?? _getCurrentContext(); final span = BugsnagPerformanceSpanImpl( - name: name, - startTime: startTime ?? _clock.now(), - onEnded: (endedSpan) async { - await _updateSamplingProbabilityIfNeeded(); - if (await _sampler?.sample(endedSpan) ?? true) { - _currentBatch?.add(endedSpan); - } - }, - parentSpanId: parent?.spanId); + name: name, + startTime: startTime ?? _clock.now(), + onEnded: (endedSpan) async { + await _updateSamplingProbabilityIfNeeded(); + if (await _sampler?.sample(endedSpan) ?? true) { + _currentBatch?.add(endedSpan); + } + }, + parentSpanId: parent?.spanId, + attributes: attributes, + ); span.clock = _clock; if (configuration != null) { _currentBatch ??= SpanBatchImpl(); @@ -95,6 +114,16 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { return span; } + @override + Future measureRunApp(FutureOr Function() runApp) async { + _appStartInstrumentation.willExecuteRunApp(); + try { + await runApp(); + } finally { + _appStartInstrumentation.didExecuteRunApp(); + } + } + void _setup() { _sampler = SamplerImpl( configuration: configuration!, diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index 67f39a1..156d726 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -5,6 +5,7 @@ class BugsnagPerformanceConfiguration { int autoTriggerExportOnBatchSize = 100; int probabilityRequestsPause = 30000; int probabilityValueExpireTime = 24 * 3600 * 1000; + bool instrumentAppStart = true; void applyExtraConfig(String key, dynamic value) { switch (key) { @@ -17,6 +18,9 @@ class BugsnagPerformanceConfiguration { case 'probabilityValueExpireTime': probabilityValueExpireTime = value; break; + case 'instrumentAppStart': + instrumentAppStart = value; + break; } } } diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart new file mode 100644 index 0000000..b4ce4e1 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart @@ -0,0 +1,98 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; +import 'package:flutter/scheduler.dart'; + +abstract class AppStartInstrumentation { + void didStartBugsnagPerformance(); + void willExecuteRunApp(); + void didExecuteRunApp(); + void setEnabled(bool enabled); +} + +class AppStartInstrumentationImpl implements AppStartInstrumentation { + final BugsnagPerformanceClient client; + + BugsnagPerformanceSpan? flutterInitSpan; + BugsnagPerformanceSpan? preRunAppPhaseSpan; + BugsnagPerformanceSpan? runAppPhaseSpan; + BugsnagPerformanceSpan? uiInitPhaseSpan; + var enabled = true; + + AppStartInstrumentationImpl({required this.client}); + + @override + void didStartBugsnagPerformance() { + if (!enabled) { + return; + } + if (flutterInitSpan != null) { + return; + } + flutterInitSpan = client.startSpan( + '[AppStart/FlutterInit]', + attributes: BugsnagPerformanceSpanAttributes(category: 'app_start'), + ); + preRunAppPhaseSpan = client.startSpan( + '[AppStartPhase/pre runApp()]', + parentContext: flutterInitSpan, + attributes: BugsnagPerformanceSpanAttributes( + category: 'app_start_phase', + phase: 'pre runApp()', + ), + ); + } + + @override + void willExecuteRunApp() { + if (!enabled) { + return; + } + if (flutterInitSpan == null) { + return; + } + preRunAppPhaseSpan?.end(); + runAppPhaseSpan = client.startSpan( + '[AppStartPhase/runApp()]', + parentContext: flutterInitSpan, + attributes: BugsnagPerformanceSpanAttributes( + category: 'app_start_phase', + phase: 'runApp()', + ), + ); + } + + @override + void didExecuteRunApp() { + if (!enabled) { + return; + } + if (flutterInitSpan == null) { + return; + } + runAppPhaseSpan?.end(); + uiInitPhaseSpan = client.startSpan( + '[AppStartPhase/UI init]', + parentContext: flutterInitSpan, + attributes: BugsnagPerformanceSpanAttributes( + category: 'app_start_phase', + phase: 'UI init', + ), + ); + SchedulerBinding.instance.addPostFrameCallback((_) { + uiInitPhaseSpan?.end(); + flutterInitSpan?.end(); + }); + } + + @override + void setEnabled(bool enabled) { + this.enabled = enabled; + if (!enabled) { + flutterInitSpan = null; + preRunAppPhaseSpan = null; + runAppPhaseSpan = null; + uiInitPhaseSpan = null; + } + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index 1c3e42b..b2ac779 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -3,11 +3,13 @@ class BugsnagPerformanceSpanAttributes { this.category = 'custom', this.isFirstClass, this.samplingProbability = 1.0, + this.phase, }); final String category; final bool? isFirstClass; double samplingProbability; + final String? phase; BugsnagPerformanceSpanAttributes.fromJson(dynamic json) : category = _value( @@ -26,7 +28,12 @@ class BugsnagPerformanceSpanAttributes { key: 'bugsnag.sampling.p', type: _ParameterType.double, ) as double? ?? - 1.0; + 1.0, + phase = _value( + json: json, + key: 'bugsnag.phase', + type: _ParameterType.string, + ) as String?; dynamic toJson() => [ { @@ -48,6 +55,13 @@ class BugsnagPerformanceSpanAttributes { 'doubleValue': samplingProbability, }, }, + if (phase != null) + { + 'key': 'bugsnag.phase', + 'value': { + 'stringValue': phase, + } + } ]; } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart index 62a17fe..f81c9f6 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/sampler.dart @@ -93,7 +93,7 @@ class SamplerImpl implements Sampler { duration = durationFromHeader; } } - probabilityStore.store(probability, + await probabilityStore.store(probability, clock.now().add(Duration(milliseconds: (duration * 1000).toInt()))); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart index f25ef39..e6fd1a6 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/sampling_probability_store.dart @@ -6,7 +6,7 @@ import 'package:path_provider/path_provider.dart'; abstract class SamplingProbabilityStore { Future get samplingProbability; - void store( + Future store( double samplingProbability, DateTime expireDate, ); @@ -34,7 +34,7 @@ class SamplingProbabilityStoreImpl implements SamplingProbabilityStore { } @override - void store( + Future store( double samplingProbability, DateTime expireDate, ) async { From 0de296461c124d4297929fd7d8e00f0791421104 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:30:05 +0100 Subject: [PATCH 23/66] Added MAZE_REPEATER_API_KEY to docker-compose.yml environment (#24) Co-authored-by: Robert --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index ba7d647..355d6a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: BUILDKITE_RETRY_COUNT: BUILDKITE_STEP_KEY: MAZE_BUGSNAG_API_KEY: + MAZE_REPEATER_API_KEY: ports: - "9000-9499:9339" volumes: From e2c14037bca8dc1b7072403c30ae9456f89f2aa6 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 8 Feb 2024 15:56:36 +0100 Subject: [PATCH 24/66] PLAT-11349 network spans (#25) --- .gitmodules | 3 + .../bugsnag_performance_example/lib/main.dart | 21 +++-- .../bugsnag_performance_example/pubspec.yaml | 4 +- .../scenarios/http_callback_cancel_span.dart | 23 +++++ .../http_callback_edit_scenario.dart | 22 +++++ .../lib/scenarios/http_get_scenario.dart | 15 ++++ .../lib/scenarios/http_post_scenario.dart | 15 ++++ .../lib/scenarios/scenarios.dart | 10 ++- features/network_spans.feature | 58 +++++++++++++ features/scripts/generate_fixture.sh | 5 ++ packages/bugsnag-flutter-http-client | 1 + .../lib/src/bugsnag_network_request_info.dart | 4 + .../src/bugsnag_performance_public_api.dart | 21 ++++- .../lib/src/client.dart | 66 +++++++++++++- .../lib/src/span.dart | 22 ++++- .../lib/src/span_attributes.dart | 86 +++++++++++++++++-- 16 files changed, 353 insertions(+), 23 deletions(-) create mode 100644 .gitmodules create mode 100644 features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart create mode 100644 features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/http_get_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/http_post_scenario.dart create mode 100644 features/network_spans.feature create mode 160000 packages/bugsnag-flutter-http-client create mode 100644 packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..976eb29 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/bugsnag-flutter-http-client"] + path = packages/bugsnag-flutter-http-client + url = git@github.com:bugsnag/bugsnag-flutter-http-client.git diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index a04ff14..91ffa67 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; import 'package:flutter/material.dart'; const apiKey = 'add_your_api_key_here'; @@ -16,15 +17,25 @@ class MainApp extends StatelessWidget { home: Scaffold( body: Center( child: TextButton( - onPressed: startTestsSpan, child: Text('send test spans'))), + onPressed: sendTestSpan, child: Text('send test span'))), ), ); } } -void startTestsSpan() { +void sendTestSpan() { BugsnagPerformance.start(apiKey: apiKey); - for (var i = 0; i < 200; i++) { - BugsnagPerformance.startSpan("test " + i.toString()).end(); - } + BugsnagPerformance.startSpan('test').end(); +} + +void sendNetworkSpan() { + BugsnagPerformance.start( + apiKey: apiKey, + networkRequestCallback: (info) { + info.url = "sanitised_url"; + return info; + }); + BugSnagHttpClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .get(Uri.parse("https://www.google.com")); } diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml index 6bff1b6..331eb4d 100644 --- a/examples/bugsnag_performance_example/pubspec.yaml +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -30,8 +30,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - - http: ^0.13.4 + bugsnag_http_client: + path: ../../packages/bugsnag-flutter-http-client dev_dependencies: flutter_test: sdk: flutter diff --git a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart new file mode 100644 index 0000000..1094e07 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart @@ -0,0 +1,23 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class HttpCallbackCancelSpanScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + await BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + networkRequestCallback: (info) { + return null; + }); + setBatchSize(1); + BugSnagHttpClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .get(FixtureConfig.MAZE_HOST); + await Future.delayed(const Duration(seconds: 10)); + BugsnagPerformance.startSpan('HttpCallbackCancelSpanScenario').end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart new file mode 100644 index 0000000..0d7561e --- /dev/null +++ b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart @@ -0,0 +1,22 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class HttpCallbackEditScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + await BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + networkRequestCallback: (info) { + info.url = "edited"; + return info; + }); + setBatchSize(1); + BugSnagHttpClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .get(FixtureConfig.MAZE_HOST); + } +} diff --git a/features/fixture_resources/lib/scenarios/http_get_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_scenario.dart new file mode 100644 index 0000000..4e22531 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/http_get_scenario.dart @@ -0,0 +1,15 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class HttpGetScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(1); + BugSnagHttpClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .get(FixtureConfig.MAZE_HOST); + } +} diff --git a/features/fixture_resources/lib/scenarios/http_post_scenario.dart b/features/fixture_resources/lib/scenarios/http_post_scenario.dart new file mode 100644 index 0000000..212de6e --- /dev/null +++ b/features/fixture_resources/lib/scenarios/http_post_scenario.dart @@ -0,0 +1,15 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class HttpPostScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(1); + BugSnagHttpClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .post(FixtureConfig.MAZE_HOST, body: {"key": "value"}); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 46628d2..ee24abe 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -7,6 +7,10 @@ import 'simple_nested_span_scenario.dart'; import 'new_zone_new_context_scenario.dart'; import 'pass_context_scenario.dart'; import 'make_current_context.dart'; +import 'http_get_scenario.dart'; +import 'http_post_scenario.dart'; +import 'http_callback_edit_scenario.dart'; +import 'http_callback_cancel_span.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -28,5 +32,9 @@ final List> scenarios = [ ScenarioInfo( 'MakeCurrentContextScenario', () => MakeCurrentContextScenario()), ScenarioInfo('AutoInstrumentAppStartsScenario', - () => AutoInstrumentAppStartsScenario()) + () => AutoInstrumentAppStartsScenario()), + ScenarioInfo('HttpGetScenario', () => HttpGetScenario()), + ScenarioInfo('HttpPostScenario', () => HttpPostScenario()), + ScenarioInfo('HttpCallbackEditScenario', () => HttpCallbackEditScenario()), + ScenarioInfo('HttpCallbackCancelSpan', () => HttpCallbackCancelSpanScenario()) ]; diff --git a/features/network_spans.feature b/features/network_spans.feature new file mode 100644 index 0000000..04b796b --- /dev/null +++ b/features/network_spans.feature @@ -0,0 +1,58 @@ +Feature: Network Spans + + Scenario: HTTP Get + When I run "HttpGetScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" matches the regex "^http:\/\/\S*:\d{4}(\/.*)?" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: HTTP Post + When I run "HttpPostScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/POST" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "POST" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" matches the regex "^http:\/\/\S*:\d{4}(\/.*)?" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.request_content_length" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: HTTP Callback Url Edit + When I run "HttpCallbackEditScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" equals "edited" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: HTTP Callback Cancel Span + When I run "HttpCallbackCancelSpan" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HttpCallbackCancelSpanScenario" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "custom" + + + + \ No newline at end of file diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index aa1cd0e..a3a8100 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -9,6 +9,9 @@ FIXTURE_LOCATION=features/fixtures/mazerunner PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" +HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" + + EXPORT_OPTIONS=features/fixture_resources/exportOptions.plist XCODE_PROJECT=features/fixtures/mazerunner/ios/Runner.xcodeproj/project.pbxproj @@ -43,6 +46,8 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" http $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" native_flutter_proxy +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_http_client:{'path':'$HTTP_WRAPPER_PACKAGE_PATH'}" + echo "Add dev team to Xcode project" sed -i '' "s/ENABLE_BITCODE = NO;/ENABLE_BITCODE = NO;\nDEVELOPMENT_TEAM = 7W9PZ27Y5F;\nCODE_SIGN_STYLE = Automatic;/g" "$XCODE_PROJECT" diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client new file mode 160000 index 0000000..85e851e --- /dev/null +++ b/packages/bugsnag-flutter-http-client @@ -0,0 +1 @@ +Subproject commit 85e851ee0e81d5cb011a655da76ec48d23e9752b diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart new file mode 100644 index 0000000..2d80afc --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart @@ -0,0 +1,4 @@ +class BugsnagNetworkRequestInfo { + String? url; + BugsnagNetworkRequestInfo({this.url}); +} diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index fba02a8..6dfbe95 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -3,14 +3,22 @@ import 'dart:async'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; import '../bugsnag_flutter_performance.dart'; +import 'bugsnag_network_request_info.dart'; import 'client.dart'; class BugsnagPerformance { static final BugsnagPerformanceClientImpl _client = BugsnagPerformanceClientImpl(); - static Future start({String? apiKey, Uri? endpoint}) async { - return _client.start(apiKey: apiKey, endpoint: endpoint); + static Future start( + {String? apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback}) async { + return _client.start( + apiKey: apiKey, + endpoint: endpoint, + networkRequestCallback: networkRequestCallback); } static BugsnagPerformanceSpan startSpan(String name, @@ -25,6 +33,11 @@ class BugsnagPerformance { ); } + static BugsnagPerformanceSpan startNetworkSpan( + String url, String httpMethod) { + return _client.startNetworkSpan(url, httpMethod.toUpperCase()); + } + static Future measureRunApp( FutureOr Function() runApp, ) async { @@ -34,4 +47,8 @@ class BugsnagPerformance { static void setExtraConfig(String key, dynamic value) { _client.setExtraConfig(key, value); } + + static dynamic networkInstrumentation(dynamic data) { + return _client.networkInstrumentation(data); + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index fba286a..63fa47e 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -14,14 +14,19 @@ import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; - +import 'bugsnag_network_request_info.dart'; import 'configuration.dart'; import 'span.dart'; const _defaultEndpoint = 'https://otlp.bugsnag.com/v1/traces'; abstract class BugsnagPerformanceClient { - Future start({String? apiKey, Uri? endpoint}); + Future start( + {String? apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback}); + Future measureRunApp(FutureOr Function() runApp); BugsnagPerformanceSpan startSpan( String name, { @@ -30,6 +35,8 @@ abstract class BugsnagPerformanceClient { bool? makeCurrentContext = true, BugsnagPerformanceSpanAttributes? attributes, }); + BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod); + dynamic networkInstrumentation(dynamic); } class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { @@ -46,6 +53,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { late final SamplingProbabilityStore _probabilityStore; late final AppStartInstrumentation _appStartInstrumentation; final Map _zoneContextStacks = {}; + final Map _networkSpans = {}; + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + _networkRequestCallback; BugsnagPerformanceClientImpl() { retryQueueBuilder = RetryQueueBuilderImpl(); @@ -59,7 +69,12 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } @override - Future start({String? apiKey, Uri? endpoint}) async { + Future start( + {String? apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback}) async { + _networkRequestCallback = networkRequestCallback; configuration = BugsnagPerformanceConfiguration( apiKey: apiKey, endpoint: endpoint ?? Uri.parse(_defaultEndpoint), @@ -223,4 +238,49 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagPerformanceSpanContext? _getCurrentContext() { return _getContextStack()?.getCurrentContext(); } + + @override + BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod) { + return startSpan("HTTP/$httpMethod", + makeCurrentContext: false, + attributes: BugsnagPerformanceSpanAttributes( + category: "network", httpMethod: httpMethod, url: url)); + } + + @override + dynamic networkInstrumentation(dynamic data) { + if (data is! Map) return true; + String status = data["status"]; + String requestId = data["request_id"]; + + if (status == "started") { + String url = data["url"]; + if (_networkRequestCallback != null) { + BugsnagNetworkRequestInfo requestInfo = + BugsnagNetworkRequestInfo(url: url); + BugsnagNetworkRequestInfo? modifiedRequestInfo = + _networkRequestCallback!(requestInfo); + if (modifiedRequestInfo?.url == null || + modifiedRequestInfo!.url!.isEmpty) { + return false; + } + url = modifiedRequestInfo.url!; + } + final span = startNetworkSpan(url, data['http_method']); + _networkSpans[requestId] = span; + } else if (status == "complete") { + final span = _networkSpans[requestId]; + if (span != null) { + span.end( + httpStatusCode: data["status_code"], + requestContentLength: data["request_content_length"], + responseContentLength: data["response_content_length"], + ); + _networkSpans.remove(requestId); + } + } else { + _networkSpans.remove(requestId); + } + return true; + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 2cf6e30..a8953c0 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -14,7 +14,11 @@ abstract class BugsnagPerformanceSpan implements BugsnagPerformanceSpanContext { @override SpanId get spanId; SpanId? parentSpanId; - void end(); + void end({ + int? httpStatusCode, + int? requestContentLength, + int? responseContentLength, + }); dynamic toJson(); } @@ -47,12 +51,24 @@ class BugsnagPerformanceSpanImpl late final BugsnagClock clock; @override - void end() { + void end({ + int? httpStatusCode, + int? requestContentLength, + int? responseContentLength, + }) { if (endTime != null) { return; } - endTime = clock.now(); + + // Update span attributes with network information if provided + if (httpStatusCode != null) attributes.httpStatusCode = httpStatusCode; + if (requestContentLength != null && requestContentLength > 0) { + attributes.requestContentLength = requestContentLength; + } + if (responseContentLength != null && responseContentLength > 0) { + attributes.responseContentLength = responseContentLength; + } onEnded(this); } diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index b2ac779..5f8e2f6 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -1,15 +1,24 @@ class BugsnagPerformanceSpanAttributes { - BugsnagPerformanceSpanAttributes({ - this.category = 'custom', - this.isFirstClass, - this.samplingProbability = 1.0, - this.phase, - }); + BugsnagPerformanceSpanAttributes( + {this.category = 'custom', + this.isFirstClass, + this.samplingProbability = 1.0, + this.phase, + this.url, + this.httpMethod, + this.httpStatusCode, + this.requestContentLength, + this.responseContentLength}); final String category; final bool? isFirstClass; double samplingProbability; final String? phase; + String? url; + String? httpMethod; + int? httpStatusCode; + int? requestContentLength; + int? responseContentLength; BugsnagPerformanceSpanAttributes.fromJson(dynamic json) : category = _value( @@ -33,7 +42,32 @@ class BugsnagPerformanceSpanAttributes { json: json, key: 'bugsnag.phase', type: _ParameterType.string, - ) as String?; + ) as String?, + url = _value( + json: json, + key: 'http.url', + type: _ParameterType.string, + ) as String?, + httpMethod = _value( + json: json, + key: 'http.method', + type: _ParameterType.string, + ) as String?, + httpStatusCode = _value( + json: json, + key: 'http.status_code', + type: _ParameterType.double, + ) as int?, + requestContentLength = _value( + json: json, + key: 'http.request_content_length', + type: _ParameterType.double, + ) as int?, + responseContentLength = _value( + json: json, + key: 'http.response_content_length', + type: _ParameterType.double, + ) as int?; dynamic toJson() => [ { @@ -61,6 +95,44 @@ class BugsnagPerformanceSpanAttributes { 'value': { 'stringValue': phase, } + }, + if (url != null) + { + 'key': 'http.url', + 'value': { + 'stringValue': url, + } + }, + if (httpMethod != null) + { + 'key': 'http.method', + 'value': { + 'stringValue': httpMethod, + } + }, + if (httpStatusCode != null) + { + 'key': 'http.status_code', + 'value': { + 'intValue': + httpStatusCode.toString(), //integerValue should be a string + } + }, + if (requestContentLength != null && requestContentLength != 0) + { + 'key': 'http.request_content_length', + 'value': { + 'intValue': requestContentLength + .toString(), //integerValue should be a string + } + }, + if (responseContentLength != null && responseContentLength != 0) + { + 'key': 'http.response_content_length', + 'value': { + 'intValue': responseContentLength + .toString(), //integerValue should be a string + } } ]; } From cb38861b989f26d8f30bee6b5258ba0732a87492 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 14 Feb 2024 10:58:25 +0100 Subject: [PATCH 25/66] PLAT-10199 add support for dio wrapper (#26) * add dio submodule * added dio wrapper e2e tests --- .gitmodules | 3 + .../scenarios/dio_callback_cancel_span.dart | 25 +++++++++ .../scenarios/dio_callback_edit_scenario.dart | 24 ++++++++ .../lib/scenarios/dio_get_scenario.dart | 16 ++++++ .../lib/scenarios/dio_post_scenario.dart | 16 ++++++ .../lib/scenarios/scenarios.dart | 11 +++- features/network_spans.feature | 56 +++++++++++++++++++ features/scripts/generate_fixture.sh | 7 +++ packages/bugsnag-flutter-dio-client | 1 + packages/bugsnag-flutter-http-client | 2 +- 10 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart create mode 100644 features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/dio_get_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/dio_post_scenario.dart create mode 160000 packages/bugsnag-flutter-dio-client diff --git a/.gitmodules b/.gitmodules index 976eb29..1337e94 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "packages/bugsnag-flutter-http-client"] path = packages/bugsnag-flutter-http-client url = git@github.com:bugsnag/bugsnag-flutter-http-client.git +[submodule "packages/bugsnag-flutter-dio-client"] + path = packages/bugsnag-flutter-dio-client + url = git@github.com:bugsnag/bugsnag-flutter-dio-client.git diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart new file mode 100644 index 0000000..d5acd5a --- /dev/null +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -0,0 +1,25 @@ +import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class DIOCallbackCancelSpanScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + await BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + networkRequestCallback: (info) { + return null; + }); + setBatchSize(1); + BugsnagDioClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .client + .get(FixtureConfig.MAZE_HOST.toString()); + await Future.delayed(const Duration(seconds: 10)); + BugsnagPerformance.startSpan('DIOCallbackCancelSpanScenario').end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart new file mode 100644 index 0000000..b3650d4 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart @@ -0,0 +1,24 @@ +import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class DIOCallbackEditScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + await BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + networkRequestCallback: (info) { + info.url = "edited"; + return info; + }); + setBatchSize(1); + BugsnagDioClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .client + .get(FixtureConfig.MAZE_HOST.toString()); + } +} diff --git a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart new file mode 100644 index 0000000..8893821 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart @@ -0,0 +1,16 @@ +import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class DIOGetScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(1); + BugsnagDioClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .client + .get(FixtureConfig.MAZE_HOST.toString()); + } +} diff --git a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart new file mode 100644 index 0000000..87240a4 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart @@ -0,0 +1,16 @@ +import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class DIOPostScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(1); + BugsnagDioClient() + .withSubscriber(BugsnagPerformance.networkInstrumentation) + .client + .post(FixtureConfig.MAZE_HOST.toString(), data: {"key": "value"}); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index ee24abe..1420f9f 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,4 +1,6 @@ import 'auto_instrument_app_starts_scenario.dart'; +import 'dio_callback_cancel_span.dart'; +import 'dio_callback_edit_scenario.dart'; import 'initial_p_scenario.dart'; import 'manual_span_scenario.dart'; import 'probability_expiry_scenario.dart'; @@ -11,6 +13,8 @@ import 'http_get_scenario.dart'; import 'http_post_scenario.dart'; import 'http_callback_edit_scenario.dart'; import 'http_callback_cancel_span.dart'; +import 'dio_get_scenario.dart'; +import 'dio_post_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -36,5 +40,10 @@ final List> scenarios = [ ScenarioInfo('HttpGetScenario', () => HttpGetScenario()), ScenarioInfo('HttpPostScenario', () => HttpPostScenario()), ScenarioInfo('HttpCallbackEditScenario', () => HttpCallbackEditScenario()), - ScenarioInfo('HttpCallbackCancelSpan', () => HttpCallbackCancelSpanScenario()) + ScenarioInfo( + 'HttpCallbackCancelSpan', () => HttpCallbackCancelSpanScenario()), + ScenarioInfo('DIOGetScenario', () => DIOGetScenario()), + ScenarioInfo('DIOPostScenario', () => DIOPostScenario()), + ScenarioInfo('DIOCallbackCancelSpan', () => DIOCallbackCancelSpanScenario()), + ScenarioInfo('DIOCallbackEditScenario', () => DIOCallbackEditScenario()), ]; diff --git a/features/network_spans.feature b/features/network_spans.feature index 04b796b..d9498a8 100644 --- a/features/network_spans.feature +++ b/features/network_spans.feature @@ -1,5 +1,7 @@ Feature: Network Spans + #HTTP WRAPPER + Scenario: HTTP Get When I run "HttpGetScenario" And I wait for 1 span @@ -54,5 +56,59 @@ Feature: Network Spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "custom" + #DIO WRAPPER + + Scenario: DIO Get + When I run "DIOGetScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" matches the regex "^http:\/\/\S*:\d{4}(\/.*)?" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: DIO Post + When I run "DIOPostScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/POST" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "POST" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" matches the regex "^http:\/\/\S*:\d{4}(\/.*)?" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.request_content_length" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: DIO Callback Url Edit + When I run "DIOCallbackEditScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" equals "edited" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + + Scenario: DIO Callback Cancel Span + When I run "DIOCallbackCancelSpan" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "DIOCallbackCancelSpanScenario" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "custom" \ No newline at end of file diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index a3a8100..21be029 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -11,6 +11,9 @@ PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" +DIO_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-dio-client" + + EXPORT_OPTIONS=features/fixture_resources/exportOptions.plist @@ -48,6 +51,10 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" native_flutter_proxy $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_http_client:{'path':'$HTTP_WRAPPER_PACKAGE_PATH'}" +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_dio_client:{'path':'$DIO_WRAPPER_PACKAGE_PATH'}" + + + echo "Add dev team to Xcode project" sed -i '' "s/ENABLE_BITCODE = NO;/ENABLE_BITCODE = NO;\nDEVELOPMENT_TEAM = 7W9PZ27Y5F;\nCODE_SIGN_STYLE = Automatic;/g" "$XCODE_PROJECT" diff --git a/packages/bugsnag-flutter-dio-client b/packages/bugsnag-flutter-dio-client new file mode 160000 index 0000000..85f506a --- /dev/null +++ b/packages/bugsnag-flutter-dio-client @@ -0,0 +1 @@ +Subproject commit 85f506a2c85c56493fc1b24c17ffaa99cf65912c diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index 85e851e..02cf74d 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit 85e851ee0e81d5cb011a655da76ec48d23e9752b +Subproject commit 02cf74d0c6516d8d969a20ad1f823fd174c29f10 From 4424a750fc73324b354d464e17183a2412f0c257 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 14 Feb 2024 11:03:10 +0100 Subject: [PATCH 26/66] make sure custom spans are first class and network spans have no attribute (#27) --- features/manual_span.feature | 2 +- features/network_spans.feature | 8 ++++---- .../lib/src/bugsnag_performance_public_api.dart | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/manual_span.feature b/features/manual_span.feature index f54f3ad..fdd8903 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -11,6 +11,6 @@ Feature: Manual Spans * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" does not exist + * every span bool attribute "bugsnag.span.first_class" is true * every span string attribute "bugsnag.span.category" equals "custom" * a span double attribute "bugsnag.sampling.p" equals 1.0 \ No newline at end of file diff --git a/features/network_spans.feature b/features/network_spans.feature index d9498a8..f8714b1 100644 --- a/features/network_spans.feature +++ b/features/network_spans.feature @@ -8,7 +8,7 @@ Feature: Network Spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * the trace "Bugsnag-Span-Sampling" header equals "1:1" - + * every span bool attribute "bugsnag.span.first_class" does not exist * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" @@ -22,7 +22,7 @@ Feature: Network Spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * the trace "Bugsnag-Span-Sampling" header equals "1:1" - + * every span bool attribute "bugsnag.span.first_class" does not exist * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/POST" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "POST" @@ -37,7 +37,7 @@ Feature: Network Spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * the trace "Bugsnag-Span-Sampling" header equals "1:1" - + * every span bool attribute "bugsnag.span.first_class" does not exist * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" @@ -51,7 +51,7 @@ Feature: Network Spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * the trace "Bugsnag-Span-Sampling" header equals "1:1" - + * every span bool attribute "bugsnag.span.first_class" is true * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HttpCallbackCancelSpanScenario" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "custom" diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 6dfbe95..71bc5f2 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; import '../bugsnag_flutter_performance.dart'; @@ -30,6 +31,7 @@ class BugsnagPerformance { startTime: startTime, parentContext: parentContext, makeCurrentContext: makeCurrentContext, + attributes: BugsnagPerformanceSpanAttributes(isFirstClass: true) ); } From 88bc4ca9f3d6a8c0e2e39ecd139dbdb520158f7a Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 14 Feb 2024 11:33:59 +0000 Subject: [PATCH 27/66] update sub module refs --- packages/bugsnag-flutter-dio-client | 2 +- packages/bugsnag-flutter-http-client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bugsnag-flutter-dio-client b/packages/bugsnag-flutter-dio-client index 85f506a..e03066c 160000 --- a/packages/bugsnag-flutter-dio-client +++ b/packages/bugsnag-flutter-dio-client @@ -1 +1 @@ -Subproject commit 85f506a2c85c56493fc1b24c17ffaa99cf65912c +Subproject commit e03066c7e01541a563f5d41cda8a140aa4bd484b diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index 02cf74d..a7e3290 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit 02cf74d0c6516d8d969a20ad1f823fd174c29f10 +Subproject commit a7e32901cf66d60e49549bb1a708b8dff1189f4f From 4c8408112e80d577192bb5df88c0d756e58b5f6d Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 21 Feb 2024 16:34:37 +0100 Subject: [PATCH 28/66] PLAT-11562 implement net.host.connection.type resource attribute (#29) * add net.host.connection.type * android build fix * gradle fix * podfile fix * pods in gem file * move bundle install * review changes --------- Co-authored-by: Josh Edney --- .buildkite/pipeline.yml | 4 +-- Gemfile | 2 ++ features/resource_attributes.feature | 7 ++++- features/scripts/generate_fixture.sh | 11 ++++++++ .../src/bugsnag_performance_public_api.dart | 12 ++++----- .../src/extensions/resource_attributes.dart | 27 +++++++++++++++++++ .../bugsnag_flutter_performance/pubspec.yaml | 1 + 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 31cd461..80d17de 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -26,8 +26,8 @@ steps: env: FLUTTER_BIN: "/opt/flutter/3.10.0/bin/flutter" commands: - - pod repo update trunk - bundle install + - pod repo update trunk - features/scripts/generate_fixture.sh - features/scripts/build_ios_app.sh plugins: @@ -106,4 +106,4 @@ steps: - "--fail-fast" concurrency: 25 concurrency_group: 'bitbar' - concurrency_method: eager \ No newline at end of file + concurrency_method: eager diff --git a/Gemfile b/Gemfile index 35982e4..3752bcf 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source 'https://rubygems.org' +gem 'cocoapods' + # A reference to Maze Runner is only needed for running tests locally and if committed it must be # portable for CI, e.g. a specific release. However, leaving it commented out would mean quicker CI. gem 'bugsnag-maze-runner', '~> 8.13.2' diff --git a/features/resource_attributes.feature b/features/resource_attributes.feature index 16e7584..cd3585b 100644 --- a/features/resource_attributes.feature +++ b/features/resource_attributes.feature @@ -19,6 +19,8 @@ Feature: Resource Attributes * the trace payload field "resourceSpans.0.resource" string attribute "device.manufacturer" exists * the trace payload field "resourceSpans.0.resource" string attribute "host.arch" exists * the trace payload field "resourceSpans.0.resource" string attribute "os.version" exists + * the trace payload field "resourceSpans.0.resource" string attribute "net.host.connection.type" exists + @android_only Scenario: Android Attributes @@ -35,6 +37,8 @@ Feature: Resource Attributes * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.platform" equals "android" * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.version_code" equals "1" * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.device.android_api_version" exists + * the trace payload field "resourceSpans.0.resource" string attribute "net.host.connection.type" exists + @ios_only Scenario: iOS Attributes @@ -49,4 +53,5 @@ Feature: Resource Attributes * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.platform" equals "ios" - * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "1" \ No newline at end of file + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "1" + * the trace payload field "resourceSpans.0.resource" string attribute "net.host.connection.type" exists diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index 21be029..331756f 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -31,6 +31,11 @@ BS_DART_LOCATION=features/fixture_resources/lib BS_DART_DESTINATION=features/fixtures/mazerunner +ANDROID_GRADLE=features/fixtures/mazerunner/android/app/build.gradle + +PODFILE=features/fixtures/mazerunner/ios/Podfile + + echo "Remove old fixture" rm -rf $FIXTURE_LOCATION @@ -53,7 +58,13 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_http_client:{'path $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_dio_client:{'path':'$DIO_WRAPPER_PACKAGE_PATH'}" +echo "update min sdk version in android gradle file" + +sed -i '' 's/minSdkVersion flutter.minSdkVersion/minSdkVersion 19/g' "$ANDROID_GRADLE" + +echo "Add min platform to pod file" +sed -i '' "s/# platform :ios, '11.0'/platform :ios, '12.0'/" "$PODFILE" echo "Add dev team to Xcode project" diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 71bc5f2..7b2e795 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -26,13 +26,11 @@ class BugsnagPerformance { {DateTime? startTime, BugsnagPerformanceSpanContext? parentContext, bool? makeCurrentContext = true}) { - return _client.startSpan( - name, - startTime: startTime, - parentContext: parentContext, - makeCurrentContext: makeCurrentContext, - attributes: BugsnagPerformanceSpanAttributes(isFirstClass: true) - ); + return _client.startSpan(name, + startTime: startTime, + parentContext: parentContext, + makeCurrentContext: makeCurrentContext, + attributes: BugsnagPerformanceSpanAttributes(isFirstClass: true)); } static BugsnagPerformanceSpan startNetworkSpan( diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart index ff43fc8..ba6f316 100644 --- a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart @@ -2,6 +2,7 @@ import 'package:device_info/device_info.dart'; import 'dart:io'; import 'package:package_info/package_info.dart'; import 'package:bugsnag_flutter_performance/src/device_id_manager.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; abstract class ResourceAttributesProvider { Future>> resourceAttributes(); @@ -18,9 +19,35 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { await _initializeResourceAttributes(); _didInitializeAttributes = true; } + await _addNetworkStatus(); return _resourceAttributes; } + Future _addNetworkStatus() async { + var status = "unavailable"; + final connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.mobile) { + status = "cell"; + } else if (connectivityResult == ConnectivityResult.wifi) { + status = "wifi"; + } + + Map networkStatusAttribute = { + 'key': 'net.host.connection.type', + 'value': { + 'stringValue': status, + } + }; + + final existingIndex = _resourceAttributes + .indexWhere((attr) => attr['key'] == 'net.host.connection.type'); + if (existingIndex != -1) { + _resourceAttributes[existingIndex] = networkStatusAttribute; + } else { + _resourceAttributes.add(networkStatusAttribute); + } + } + Future _initializeResourceAttributes() async { final deviceInfo = DeviceInfoPlugin(); final packageInfo = await PackageInfo.fromPlatform(); diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index af3af19..ca3d805 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: package_info: ^2.0.2 path_provider: ^2.0.2 uuid: ^4.0.0 + connectivity_plus: ^5.0.2 dev_dependencies: flutter_test: From 977226ca5941bab9b24e1b7a5960e4addf192b4f Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 21 Feb 2024 16:44:54 +0100 Subject: [PATCH 29/66] add privacy manifest (#30) --- .../ios/Resources/PrivacyInfo.xcprivacy | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy diff --git a/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy b/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..c03d390 --- /dev/null +++ b/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,45 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDataTypes + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + From 497d3d2b3701070f65e2a4d27302143d2304fd89 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 29 Feb 2024 17:48:33 +0100 Subject: [PATCH 30/66] PLAT-11215 discard spans when the app goes into the background (#28) * initial commit * unit tests fix * added flag * review fixes * unity test * lint fix --- .../lib/src/client.dart | 24 +++++++++++- .../bugsnag_lifecycle_listener.dart | 38 +++++++++++++++++++ .../lib/src/span.dart | 14 ++++++- .../test/src/client_test.dart | 29 ++++++++++++-- .../test/src/span_test.dart | 2 + 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/lib/src/extensions/bugsnag_lifecycle_listener.dart diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 63fa47e..79eab89 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/app_start_instrumentation.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; @@ -49,6 +50,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { DateTime? _lastSamplingProbabilityRefreshDate; late final PackageBuilder _packageBuilder; late final BugsnagClock _clock; + late final BugsnagLifecycleListener? _lifecycleListener; final Map _initialExtraConfig = {}; late final SamplingProbabilityStore _probabilityStore; late final AppStartInstrumentation _appStartInstrumentation; @@ -56,8 +58,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final Map _networkSpans = {}; BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? _networkRequestCallback; + final Map _potentiallyOpenSpans = {}; - BugsnagPerformanceClientImpl() { + BugsnagPerformanceClientImpl({BugsnagLifecycleListener? lifecycleListener}) { retryQueueBuilder = RetryQueueBuilderImpl(); BugsnagClockImpl.ensureInitialized(); _packageBuilder = PackageBuilderImpl( @@ -66,6 +69,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _clock = BugsnagClockImpl.instance; _probabilityStore = SamplingProbabilityStoreImpl(_clock); _appStartInstrumentation = AppStartInstrumentationImpl(client: this); + BugsnagLifecycleListenerImpl.ensureInitialized(); + _lifecycleListener = + lifecycleListener ?? BugsnagLifecycleListenerImpl.instance; } @override @@ -87,6 +93,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _setup(); _appStartInstrumentation.didStartBugsnagPerformance(); await _retryQueue?.flush(); + _lifecycleListener?.startObserving(onAppBackgrounded: _onAppBackgrounded); } @override @@ -111,6 +118,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { if (await _sampler?.sample(endedSpan) ?? true) { _currentBatch?.add(endedSpan); } + _potentiallyOpenSpans.remove(endedSpan.spanId); + }, + onCanceled: (cancledSpan) { + _potentiallyOpenSpans.remove(cancledSpan.spanId); }, parentSpanId: parent?.spanId, attributes: attributes, @@ -125,7 +136,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { if (makeCurrentContext == true) { _addContext(span); } - + _potentiallyOpenSpans[span.spanId] = span; return span; } @@ -283,4 +294,13 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } return true; } + + void _onAppBackgrounded() { + var keys = List.from(_potentiallyOpenSpans.keys); + for (var key in keys) { + _potentiallyOpenSpans[key]?.end(cancelled: true); + _potentiallyOpenSpans.remove(key); + } + _potentiallyOpenSpans.clear(); + } } diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/bugsnag_lifecycle_listener.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/bugsnag_lifecycle_listener.dart new file mode 100644 index 0000000..3926298 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/bugsnag_lifecycle_listener.dart @@ -0,0 +1,38 @@ +import 'package:flutter/widgets.dart'; + +abstract class BugsnagLifecycleListener { + void startObserving({required void Function() onAppBackgrounded}); +} + +class BugsnagLifecycleListenerImpl + with WidgetsBindingObserver + implements BugsnagLifecycleListener { + static BugsnagLifecycleListenerImpl get instance => _instance!; + static BugsnagLifecycleListenerImpl? _instance; + bool _isObserving = false; + + void Function()? _onAppBackgrounded; + + static BugsnagLifecycleListener ensureInitialized() { + _instance = _instance ?? BugsnagLifecycleListenerImpl(); + return instance; + } + + @override + void startObserving({required void Function() onAppBackgrounded}) { + if (_isObserving) { + return; + } + _isObserving = true; + _onAppBackgrounded = onAppBackgrounded; + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.paused) { + _onAppBackgrounded?.call(); + } + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index a8953c0..8cfb3c3 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -18,6 +18,7 @@ abstract class BugsnagPerformanceSpan implements BugsnagPerformanceSpanContext { int? httpStatusCode, int? requestContentLength, int? responseContentLength, + bool cancelled = false, }); dynamic toJson(); } @@ -28,6 +29,7 @@ class BugsnagPerformanceSpanImpl {required this.name, required this.startTime, void Function(BugsnagPerformanceSpan)? onEnded, + void Function(BugsnagPerformanceSpan)? onCanceled, TraceId? traceId, SpanId? spanId, this.parentSpanId, @@ -35,6 +37,7 @@ class BugsnagPerformanceSpanImpl this.traceId = traceId ?? randomTraceId(); this.spanId = spanId ?? randomSpanId(); this.onEnded = onEnded ?? _onEnded; + this.onCanceled = onCanceled ?? _onCanceled; this.attributes = attributes ?? BugsnagPerformanceSpanAttributes(); } final String name; @@ -48,6 +51,7 @@ class BugsnagPerformanceSpanImpl late final BugsnagPerformanceSpanAttributes attributes; DateTime? endTime; late final void Function(BugsnagPerformanceSpan) onEnded; + late final void Function(BugsnagPerformanceSpan) onCanceled; late final BugsnagClock clock; @override @@ -55,12 +59,16 @@ class BugsnagPerformanceSpanImpl int? httpStatusCode, int? requestContentLength, int? responseContentLength, + bool cancelled = false, }) { - if (endTime != null) { + if (!isOpen()) { return; } endTime = clock.now(); - + if (cancelled) { + onCanceled(this); + return; + } // Update span attributes with network information if provided if (httpStatusCode != null) attributes.httpStatusCode = httpStatusCode; if (requestContentLength != null && requestContentLength > 0) { @@ -144,3 +152,5 @@ SpanId? _decodeSpanId(String? spanIdString) { } void _onEnded(BugsnagPerformanceSpan span) {} + +void _onCanceled(BugsnagPerformanceSpan span) {} diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index 4150b3b..37113d4 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; import 'package:bugsnag_flutter_performance/src/uploader/retry_queue.dart'; @@ -24,21 +25,34 @@ class MockRetryQueueBuilder implements RetryQueueBuilder { } } +class MockLifecycleListener implements BugsnagLifecycleListener { + void Function()? _onAppBackgrounded; + + @override + void startObserving({void Function()? onAppBackgrounded}) { + _onAppBackgrounded = onAppBackgrounded; + } + + void triggerAppBackgrounded() { + _onAppBackgrounded?.call(); + } +} + void main() { const apiKey = 'TestApiKey'; final endpoint = Uri.tryParse('https://bugsnag.com')!; BugsnagClockImpl.ensureInitialized(); group('BugsnagPerformanceClient', () { late BugsnagPerformanceClientImpl client; - + final lifecycleListener = MockLifecycleListener(); setUp(() { - client = BugsnagPerformanceClientImpl(); + client = + BugsnagPerformanceClientImpl(lifecycleListener: lifecycleListener); client.retryQueueBuilder = MockRetryQueueBuilder(); }); group('start', () { test('should set configuration with the provided parameters', () async { await client.start(apiKey: apiKey, endpoint: endpoint); - expect(client.configuration!.apiKey, equals(apiKey)); expect(client.configuration!.endpoint, equals(endpoint)); }); @@ -65,5 +79,14 @@ void main() { expect(span.endTime, isNull); }); }); + group('onAppBackgrounded', () { + test('should cancel spans when app background event triggers', () async { + await client.start(apiKey: apiKey, endpoint: endpoint); + final span = client.startSpan("test"); + expect(span.isOpen(), isTrue); + lifecycleListener.triggerAppBackgrounded(); + expect(span.isOpen(), isFalse); + }); + }); }); } diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 1fb8f91..12a88c4 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -1,3 +1,4 @@ +import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/date_time.dart'; import 'package:bugsnag_flutter_performance/src/extensions/int.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; @@ -8,6 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { const millisecondsSinceEpoch = 1640979000000; BugsnagClockImpl.ensureInitialized(); + BugsnagLifecycleListenerImpl.ensureInitialized(); group('BugsnagPerformanceSpanImpl', () { test('should have the provided name, start time, traceId and spanId', () { From 41e25d163b70d74a6dda27cbd81c103a3c82bec2 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Tue, 5 Mar 2024 10:59:37 +0100 Subject: [PATCH 31/66] PLAT-11660 allow static usage (#32) * initial working code * refactor * updated sub mod --- .../lib/scenarios/http_callback_cancel_span.dart | 7 +++---- .../scenarios/http_callback_edit_scenario.dart | 7 +++---- .../http_get_multiple_subscribers_scenario.dart | 15 +++++++++++++++ .../lib/scenarios/http_get_scenario.dart | 7 +++---- .../lib/scenarios/http_post_scenario.dart | 6 +++--- .../lib/scenarios/scenarios.dart | 3 +++ features/network_spans.feature | 10 ++++++++++ packages/bugsnag-flutter-http-client | 2 +- 8 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart index 1094e07..466fd71 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart @@ -1,5 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; import '../main.dart'; import 'scenario.dart'; @@ -14,9 +14,8 @@ class HttpCallbackCancelSpanScenario extends Scenario { return null; }); setBatchSize(1); - BugSnagHttpClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .get(FixtureConfig.MAZE_HOST); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); await Future.delayed(const Duration(seconds: 10)); BugsnagPerformance.startSpan('HttpCallbackCancelSpanScenario').end(); } diff --git a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart index 0d7561e..5168c04 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart @@ -1,5 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; import '../main.dart'; import 'scenario.dart'; @@ -15,8 +15,7 @@ class HttpCallbackEditScenario extends Scenario { return info; }); setBatchSize(1); - BugSnagHttpClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .get(FixtureConfig.MAZE_HOST); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart new file mode 100644 index 0000000..14c2a9c --- /dev/null +++ b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart @@ -0,0 +1,15 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; +import '../main.dart'; +import 'scenario.dart'; + +class HttpGetMultipleSubscribersScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setBatchSize(1); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.get(FixtureConfig.MAZE_HOST); + } +} diff --git a/features/fixture_resources/lib/scenarios/http_get_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_scenario.dart index 4e22531..7219f85 100644 --- a/features/fixture_resources/lib/scenarios/http_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_get_scenario.dart @@ -1,5 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; import '../main.dart'; import 'scenario.dart'; @@ -8,8 +8,7 @@ class HttpGetScenario extends Scenario { Future run() async { await startBugsnag(); setBatchSize(1); - BugSnagHttpClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .get(FixtureConfig.MAZE_HOST); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_post_scenario.dart b/features/fixture_resources/lib/scenarios/http_post_scenario.dart index 212de6e..8e943ec 100644 --- a/features/fixture_resources/lib/scenarios/http_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_post_scenario.dart @@ -1,5 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; import '../main.dart'; import 'scenario.dart'; @@ -8,8 +8,8 @@ class HttpPostScenario extends Scenario { Future run() async { await startBugsnag(); setBatchSize(1); - BugSnagHttpClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) + http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.BugSnagHttpClient() .post(FixtureConfig.MAZE_HOST, body: {"key": "value"}); } } diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 1420f9f..f07a6d5 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -15,6 +15,7 @@ import 'http_callback_edit_scenario.dart'; import 'http_callback_cancel_span.dart'; import 'dio_get_scenario.dart'; import 'dio_post_scenario.dart'; +import 'http_get_multiple_subscribers_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -46,4 +47,6 @@ final List> scenarios = [ ScenarioInfo('DIOPostScenario', () => DIOPostScenario()), ScenarioInfo('DIOCallbackCancelSpan', () => DIOCallbackCancelSpanScenario()), ScenarioInfo('DIOCallbackEditScenario', () => DIOCallbackEditScenario()), + ScenarioInfo('HttpGetMultipleSubscribersScenario', + () => HttpGetMultipleSubscribersScenario()), ]; diff --git a/features/network_spans.feature b/features/network_spans.feature index f8714b1..17c6ca6 100644 --- a/features/network_spans.feature +++ b/features/network_spans.feature @@ -31,6 +31,16 @@ Feature: Network Spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.request_content_length" is greater than 0 * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + #this is to make sure that mutliple spans wont be created for the same request + Scenario: HTTP Get Multiple Subscribers + When I run "HttpGetMultipleSubscribersScenario" + And I wait for 1 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span bool attribute "bugsnag.span.first_class" does not exist + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + Scenario: HTTP Callback Url Edit When I run "HttpCallbackEditScenario" And I wait for 1 span diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index a7e3290..2d1ad19 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit a7e32901cf66d60e49549bb1a708b8dff1189f4f +Subproject commit 2d1ad192b91844562a029777d49ee746545cb125 From ba7b460355968166af94c01c6653e8bf91c81b7c Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:28:37 +0100 Subject: [PATCH 32/66] Basic route navigation instrumentation (#31) Co-authored-by: Robert --- .gitmodules | 3 ++ .../bugsnag_performance_example/pubspec.yaml | 8 ++++ features/automatic_spans.feature | 13 ++++++ features/fixture_resources/lib/main.dart | 15 ++++--- ..._instrument_navigation_basic_scenario.dart | 29 +++++++++++++ .../lib/scenarios/initial_p_scenario.dart | 1 + .../lib/scenarios/scenario.dart | 1 + .../lib/scenarios/scenarios.dart | 4 ++ features/scripts/generate_fixture.sh | 6 ++- packages/bugsnag-flutter-navigator-observer | 1 + .../lib/src/client.dart | 14 +++++++ .../lib/src/configuration.dart | 4 ++ .../navigation_instrumentation.dart | 42 +++++++++++++++++++ .../bugsnag_flutter_performance/pubspec.yaml | 4 ++ 14 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart create mode 160000 packages/bugsnag-flutter-navigator-observer create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart diff --git a/.gitmodules b/.gitmodules index 1337e94..cf17a85 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "packages/bugsnag-flutter-dio-client"] path = packages/bugsnag-flutter-dio-client url = git@github.com:bugsnag/bugsnag-flutter-dio-client.git +[submodule "packages/bugsnag-flutter-navigator-observer"] + path = packages/bugsnag-flutter-navigator-observer + url=git@github.com:bugsnag/bugsnag-flutter-navigator-observer.git diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml index 331eb4d..0ab24eb 100644 --- a/examples/bugsnag_performance_example/pubspec.yaml +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -27,6 +27,14 @@ dependencies: # the parent directory to use the current plugin's version. path: ../../packages/bugsnag_flutter_performance + bugsnag_navigator_observer: + # When depending on this package from a real application you should use: + # flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../../packages/bugsnag-flutter-navigator-observer + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 diff --git a/features/automatic_spans.feature b/features/automatic_spans.feature index d40eaef..0942dbb 100644 --- a/features/automatic_spans.feature +++ b/features/automatic_spans.feature @@ -23,3 +23,16 @@ Feature: Automatic instrumentation spans * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/pre runApp()]" * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/runApp()]" * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/UI init]" + + Scenario: AutoInstrumentNavigationBasicScenario + Given I run "AutoInstrumentNavigationBasicScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[Navigation]/AutoInstrumentNavigationBasicScenarioScreen" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index 805c1ec..3a35d7a 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:native_flutter_proxy/custom_proxy.dart'; import 'package:native_flutter_proxy/native_proxy_reader.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:bugsnag_navigator_observer/bugsnag_flutter_navigator_observer.dart'; import 'package:http/http.dart' as http; import 'scenarios/scenario.dart'; @@ -92,6 +93,7 @@ class MazeRunnerFlutterApp extends StatelessWidget { theme: ThemeData( primaryColor: const Color.fromARGB(255, 73, 73, 227), ), + navigatorObservers: [BugsnagNavigatorObserver()], home: FutureBuilder( future: _getMazeRunnerUrl(), builder: (_, mazerunnerUrl) { @@ -269,17 +271,20 @@ class _HomePageState extends State { scenario.extraConfig = _extraConfigController.value.text; + log('Running scenario'); + _currentScenario = scenario; + await scenario.run(); Widget? scenarioWidget = scenario.createWidget(); if (scenarioWidget != null) { log('Mounting Scenario Widget'); - final route = MaterialPageRoute(builder: (context) => scenarioWidget); + final route = MaterialPageRoute( + builder: (context) => scenarioWidget, + settings: scenario.routeSettings(), + ); + log('Name: ${route.settings.name}'); Navigator.push(context, route); await route.didPush(); } - - log('Running scenario'); - _currentScenario = scenario; - await scenario.run(); } Scenario? _initScenario(BuildContext context) { diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart new file mode 100644 index 0000000..686b74e --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart @@ -0,0 +1,29 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentNavigationBasicScenario extends Scenario { + @override + Future run() async { + BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + BugsnagPerformance.setExtraConfig("instrumentNavigation", true); + BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 1000); + BugsnagPerformance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + setBatchSize(1); + } + + @override + Widget? createWidget() { + return Text('AutoInstrumentNavigationBasicScenario'); + } + + @override + RouteSettings? routeSettings() { + return RouteSettings(name: 'AutoInstrumentNavigationBasicScenarioScreen'); + } +} diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart index 0cbf299..809cfce 100644 --- a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -10,6 +10,7 @@ class InitialPScenario extends Scenario { @override Future run() async { BugsnagPerformance.setExtraConfig("instrumentAppStart", false); + BugsnagPerformance.setExtraConfig("instrumentNavigation", false); BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 1000); BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 25000); BugsnagPerformance.start( diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 8043ebf..594d5e0 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -14,6 +14,7 @@ abstract class Scenario { } Widget? createWidget() => null; + RouteSettings? routeSettings() => null; Future run(); diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index f07a6d5..2a1f1bd 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,3 +1,5 @@ +import 'package:mazerunner/scenarios/auto_instrument_navigation_basic_scenario.dart'; + import 'auto_instrument_app_starts_scenario.dart'; import 'dio_callback_cancel_span.dart'; import 'dio_callback_edit_scenario.dart'; @@ -49,4 +51,6 @@ final List> scenarios = [ ScenarioInfo('DIOCallbackEditScenario', () => DIOCallbackEditScenario()), ScenarioInfo('HttpGetMultipleSubscribersScenario', () => HttpGetMultipleSubscribersScenario()), + ScenarioInfo('AutoInstrumentNavigationBasicScenario', + () => AutoInstrumentNavigationBasicScenario()) ]; diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index 331756f..e2f2e97 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -7,7 +7,8 @@ fi FIXTURE_LOCATION=features/fixtures/mazerunner -PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" +PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" +NAVIGATOR_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-navigator-observer" HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" @@ -47,6 +48,7 @@ $FLUTTER_BIN create $FIXTURE_LOCATION --org com.bugsnag --platforms=ios,android echo "Add dependencies" $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_flutter_performance:{'path':'$PACKAGE_PATH'}" +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_navigator_observer:{'path':'$NAVIGATOR_PACKAGE_PATH'}" $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" path_provider @@ -84,4 +86,4 @@ rm -rf $DART_TEST_LOCATION rm -rf $DART_LOCATION -cp -r $BS_DART_LOCATION $BS_DART_DESTINATION \ No newline at end of file +cp -r $BS_DART_LOCATION $BS_DART_DESTINATION diff --git a/packages/bugsnag-flutter-navigator-observer b/packages/bugsnag-flutter-navigator-observer new file mode 160000 index 0000000..a275212 --- /dev/null +++ b/packages/bugsnag-flutter-navigator-observer @@ -0,0 +1 @@ +Subproject commit a275212a0bcef3f641dd9cbf6437404db34479ec diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 79eab89..b968352 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/app_start_instrumentation.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation_instrumentation.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; @@ -16,6 +17,9 @@ import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'bugsnag_network_request_info.dart'; +// ignore: implementation_imports +import 'package:bugsnag_navigator_observer/src/bugsnag_navigator_observer_callbacks.dart'; + import 'configuration.dart'; import 'span.dart'; @@ -54,6 +58,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final Map _initialExtraConfig = {}; late final SamplingProbabilityStore _probabilityStore; late final AppStartInstrumentation _appStartInstrumentation; + late final NavigationInstrumentation _navigationInstrumentation; final Map _zoneContextStacks = {}; final Map _networkSpans = {}; BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? @@ -72,6 +77,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { BugsnagLifecycleListenerImpl.ensureInitialized(); _lifecycleListener = lifecycleListener ?? BugsnagLifecycleListenerImpl.instance; + _navigationInstrumentation = NavigationInstrumentationImpl( + client: this, + clock: _clock, + ); } @override @@ -90,6 +99,8 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { }); _appStartInstrumentation .setEnabled(configuration?.instrumentAppStart ?? false); + _navigationInstrumentation + .setEnabled(configuration?.instrumentNavigation ?? false); _setup(); _appStartInstrumentation.didStartBugsnagPerformance(); await _retryQueue?.flush(); @@ -171,6 +182,9 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { (timer) { _updateSamplingProbabilityIfNeeded(force: true); }); + BugsnagNavigatorObserverCallbacks.setup( + willShowNewRouteCallback: _navigationInstrumentation.willShowRoute, + ); } void _sendBatch(SpanBatch batch) async { diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index 156d726..fc5f082 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -6,6 +6,7 @@ class BugsnagPerformanceConfiguration { int probabilityRequestsPause = 30000; int probabilityValueExpireTime = 24 * 3600 * 1000; bool instrumentAppStart = true; + bool instrumentNavigation = true; void applyExtraConfig(String key, dynamic value) { switch (key) { @@ -21,6 +22,9 @@ class BugsnagPerformanceConfiguration { case 'instrumentAppStart': instrumentAppStart = value; break; + case 'instrumentNavigation': + instrumentNavigation = value; + break; } } } diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart new file mode 100644 index 0000000..140ff28 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart @@ -0,0 +1,42 @@ +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; + +abstract class NavigationInstrumentation { + void setEnabled(bool enabled); + void willShowRoute( + Route? route, + String? routeDescription, + ); +} + +class NavigationInstrumentationImpl implements NavigationInstrumentation { + final BugsnagPerformanceClient client; + late final BugsnagClock clock; + + var _enabled = true; + + NavigationInstrumentationImpl({ + required this.client, + required this.clock, + }); + + @override + void setEnabled(bool enabled) { + _enabled = enabled; + } + + @override + void willShowRoute(Route? route, String? routeDescription) { + if (!_enabled || routeDescription == null) { + return; + } + final startTime = clock.now(); + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + client + .startSpan('[Navigation]/$routeDescription', startTime: startTime) + .end(); + }); + } +} diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index ca3d805..47f1095 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -2,6 +2,8 @@ name: bugsnag_flutter_performance description: A new Flutter plugin project. version: 0.0.1 homepage: +# TODO: Remove once all dependencies are published +publish_to: none environment: sdk: '>=3.0.0 <4.0.0' @@ -17,6 +19,8 @@ dependencies: path_provider: ^2.0.2 uuid: ^4.0.0 connectivity_plus: ^5.0.2 + bugsnag_navigator_observer: + path: ../bugsnag-flutter-navigator-observer dev_dependencies: flutter_test: From fd4ac3a61625a80e9da4ac9d1cf0a8f88186359f Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 7 Mar 2024 09:13:24 +0100 Subject: [PATCH 33/66] cleaned up example project --- .../bugsnag_performance_example/lib/main.dart | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index 91ffa67..f236f54 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -1,10 +1,13 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; import 'package:flutter/material.dart'; -const apiKey = 'add_your_api_key_here'; +const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + BugsnagPerformance.start(apiKey: apiKey); + http.addSubscriber(BugsnagPerformance.networkInstrumentation); runApp(const MainApp()); } @@ -13,29 +16,36 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( home: Scaffold( body: Center( - child: TextButton( - onPressed: sendTestSpan, child: Text('send test span'))), + child: Row( // Use Column for vertical alignment + mainAxisAlignment: MainAxisAlignment.center, // Center the buttons horizontally + children: [ + TextButton( + onPressed: sendCustomSpan, // Replace with your actual function + child: Text('Send Custom Span'), + ), + SizedBox(width: 20), // Spacing between buttons, adjust as needed + TextButton( + onPressed: sendNetworkSpan, // You'll need to define this function + child: Text('Send Network Span'), + ), + ], + ), + ), ), ); } -} -void sendTestSpan() { - BugsnagPerformance.start(apiKey: apiKey); - BugsnagPerformance.startSpan('test').end(); -} + void sendCustomSpan() { + BugsnagPerformance.startSpan('test').end(); + } -void sendNetworkSpan() { - BugsnagPerformance.start( - apiKey: apiKey, - networkRequestCallback: (info) { - info.url = "sanitised_url"; - return info; - }); - BugSnagHttpClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .get(Uri.parse("https://www.google.com")); + void sendNetworkSpan() { + http.get(Uri.parse('https://httpbin.org/get')); + } } + + + From 4814f34f91624c9bb3435ecce6b5af238fbc23bb Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 7 Mar 2024 09:34:18 +0100 Subject: [PATCH 34/66] ensure init (#33) --- examples/bugsnag_performance_example/lib/main.dart | 1 - packages/bugsnag_flutter_performance/lib/src/client.dart | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index f236f54..4c295c2 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { - WidgetsFlutterBinding.ensureInitialized(); BugsnagPerformance.start(apiKey: apiKey); http.addSubscriber(BugsnagPerformance.networkInstrumentation); runApp(const MainApp()); diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index b968352..dd05324 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -16,6 +16,7 @@ import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:flutter/widgets.dart'; import 'bugsnag_network_request_info.dart'; // ignore: implementation_imports import 'package:bugsnag_navigator_observer/src/bugsnag_navigator_observer_callbacks.dart'; @@ -89,6 +90,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { Uri? endpoint, BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? networkRequestCallback}) async { + WidgetsFlutterBinding.ensureInitialized(); _networkRequestCallback = networkRequestCallback; configuration = BugsnagPerformanceConfiguration( apiKey: apiKey, From 15e37cfb5b9cc0bde76e25d0aa58631dc6b23d2a Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 7 Mar 2024 10:18:58 +0100 Subject: [PATCH 35/66] add measure app to example --- examples/bugsnag_performance_example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index 4c295c2..b3c558b 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -7,7 +7,7 @@ const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { BugsnagPerformance.start(apiKey: apiKey); http.addSubscriber(BugsnagPerformance.networkInstrumentation); - runApp(const MainApp()); + BugsnagPerformance.measureRunApp(() async => runApp(const MainApp())); } class MainApp extends StatelessWidget { From e1302fe44e4ca9602a748d2302e6ee5cd7b13216 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 11 Mar 2024 08:37:29 +0100 Subject: [PATCH 36/66] add missing app start type (#36) --- features/automatic_spans.feature | 1 + .../app_start_instrumentation.dart | 5 ++++- .../lib/src/span_attributes.dart | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/features/automatic_spans.feature b/features/automatic_spans.feature index 0942dbb..c89a130 100644 --- a/features/automatic_spans.feature +++ b/features/automatic_spans.feature @@ -20,6 +20,7 @@ Feature: Automatic instrumentation spans * a span string attribute "bugsnag.span.category" equals "app_start" * a span string attribute "bugsnag.span.category" equals "app_start_phase" * every span bool attribute "bugsnag.span.first_class" does not exist + * every span string attribute "bugsnag.app_start.type" equals "FlutterInit" * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/pre runApp()]" * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/runApp()]" * the span named "[AppStart/FlutterInit]" is the parent of the span named "[AppStartPhase/UI init]" diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart index b4ce4e1..96d6f10 100644 --- a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart @@ -31,7 +31,7 @@ class AppStartInstrumentationImpl implements AppStartInstrumentation { } flutterInitSpan = client.startSpan( '[AppStart/FlutterInit]', - attributes: BugsnagPerformanceSpanAttributes(category: 'app_start'), + attributes: BugsnagPerformanceSpanAttributes(category: 'app_start', appStartType: 'FlutterInit'), ); preRunAppPhaseSpan = client.startSpan( '[AppStartPhase/pre runApp()]', @@ -39,6 +39,7 @@ class AppStartInstrumentationImpl implements AppStartInstrumentation { attributes: BugsnagPerformanceSpanAttributes( category: 'app_start_phase', phase: 'pre runApp()', + appStartType: 'FlutterInit', ), ); } @@ -58,6 +59,7 @@ class AppStartInstrumentationImpl implements AppStartInstrumentation { attributes: BugsnagPerformanceSpanAttributes( category: 'app_start_phase', phase: 'runApp()', + appStartType: 'FlutterInit', ), ); } @@ -77,6 +79,7 @@ class AppStartInstrumentationImpl implements AppStartInstrumentation { attributes: BugsnagPerformanceSpanAttributes( category: 'app_start_phase', phase: 'UI init', + appStartType: 'FlutterInit', ), ); SchedulerBinding.instance.addPostFrameCallback((_) { diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index 5f8e2f6..edb905a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -8,7 +8,8 @@ class BugsnagPerformanceSpanAttributes { this.httpMethod, this.httpStatusCode, this.requestContentLength, - this.responseContentLength}); + this.responseContentLength, + this.appStartType}); final String category; final bool? isFirstClass; @@ -19,6 +20,7 @@ class BugsnagPerformanceSpanAttributes { int? httpStatusCode; int? requestContentLength; int? responseContentLength; + final String? appStartType; BugsnagPerformanceSpanAttributes.fromJson(dynamic json) : category = _value( @@ -43,6 +45,11 @@ class BugsnagPerformanceSpanAttributes { key: 'bugsnag.phase', type: _ParameterType.string, ) as String?, + appStartType = _value( + json: json, + key: 'bugsnag.app_start.type', + type: _ParameterType.string, + ) as String?, url = _value( json: json, key: 'http.url', @@ -96,6 +103,13 @@ class BugsnagPerformanceSpanAttributes { 'stringValue': phase, } }, + if (appStartType != null) + { + 'key': 'bugsnag.app_start.type', + 'value': { + 'stringValue': appStartType, + } + }, if (url != null) { 'key': 'http.url', From cd84b7cc30e739832d3a5482149ffa68873459c7 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 11 Mar 2024 15:03:04 +0100 Subject: [PATCH 37/66] PLAT-11737 send batch after timeout (#34) --- .../auto_instrument_app_starts_scenario.dart | 2 +- ..._instrument_navigation_basic_scenario.dart | 2 +- .../scenarios/dio_callback_cancel_span.dart | 2 +- .../scenarios/dio_callback_edit_scenario.dart | 2 +- .../lib/scenarios/dio_get_scenario.dart | 2 +- .../lib/scenarios/dio_post_scenario.dart | 2 +- .../scenarios/http_callback_cancel_span.dart | 2 +- .../http_callback_edit_scenario.dart | 2 +- ...ttp_get_multiple_subscribers_scenario.dart | 2 +- .../lib/scenarios/http_get_scenario.dart | 2 +- .../lib/scenarios/http_post_scenario.dart | 2 +- .../lib/scenarios/initial_p_scenario.dart | 2 +- .../lib/scenarios/make_current_context.dart | 2 +- .../lib/scenarios/manual_span_scenario.dart | 2 +- .../lib/scenarios/max_batch_age_scenario.dart | 12 ++++++++++ .../new_zone_new_context_scenario.dart | 2 +- .../lib/scenarios/pass_context_scenario.dart | 2 +- .../probability_expiry_scenario.dart | 2 +- .../lib/scenarios/scenario.dart | 8 +++++-- .../lib/scenarios/scenarios.dart | 5 ++++- .../simple_nested_span_scenario.dart | 2 +- features/manual_span.feature | 10 ++++++++- .../lib/src/configuration.dart | 10 ++++++--- .../lib/src/uploader/span_batch.dart | 22 ++++++++++++++++--- .../test/src/uploader/span_batch_test.dart | 3 +-- 25 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart index 330f08c..f3b9880 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart @@ -12,6 +12,6 @@ class AutoInstrumentAppStartsScenario extends Scenario { apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); BugsnagPerformance.measureRunApp(() async => await Duration(seconds: 1)); - setBatchSize(4); + setMaxBatchSize(4); } } diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart index 686b74e..83d8375 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart @@ -14,7 +14,7 @@ class AutoInstrumentNavigationBasicScenario extends Scenario { BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); - setBatchSize(1); + setMaxBatchSize(1); } @override diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart index d5acd5a..b1fa9c0 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -14,7 +14,7 @@ class DIOCallbackCancelSpanScenario extends Scenario { networkRequestCallback: (info) { return null; }); - setBatchSize(1); + setMaxBatchSize(1); BugsnagDioClient() .withSubscriber(BugsnagPerformance.networkInstrumentation) .client diff --git a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart index b3650d4..3354344 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart @@ -15,7 +15,7 @@ class DIOCallbackEditScenario extends Scenario { info.url = "edited"; return info; }); - setBatchSize(1); + setMaxBatchSize(1); BugsnagDioClient() .withSubscriber(BugsnagPerformance.networkInstrumentation) .client diff --git a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart index 8893821..eb15511 100644 --- a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart @@ -7,7 +7,7 @@ class DIOGetScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); BugsnagDioClient() .withSubscriber(BugsnagPerformance.networkInstrumentation) .client diff --git a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart index 87240a4..b1cfbda 100644 --- a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart @@ -7,7 +7,7 @@ class DIOPostScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); BugsnagDioClient() .withSubscriber(BugsnagPerformance.networkInstrumentation) .client diff --git a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart index 466fd71..a155588 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart @@ -13,7 +13,7 @@ class HttpCallbackCancelSpanScenario extends Scenario { networkRequestCallback: (info) { return null; }); - setBatchSize(1); + setMaxBatchSize(1); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); await Future.delayed(const Duration(seconds: 10)); diff --git a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart index 5168c04..703bb7d 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart @@ -14,7 +14,7 @@ class HttpCallbackEditScenario extends Scenario { info.url = "edited"; return info; }); - setBatchSize(1); + setMaxBatchSize(1); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); } diff --git a/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart index 14c2a9c..fd0f58b 100644 --- a/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart @@ -7,7 +7,7 @@ class HttpGetMultipleSubscribersScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.get(FixtureConfig.MAZE_HOST); diff --git a/features/fixture_resources/lib/scenarios/http_get_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_scenario.dart index 7219f85..f63805c 100644 --- a/features/fixture_resources/lib/scenarios/http_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_get_scenario.dart @@ -7,7 +7,7 @@ class HttpGetScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.get(FixtureConfig.MAZE_HOST); } diff --git a/features/fixture_resources/lib/scenarios/http_post_scenario.dart b/features/fixture_resources/lib/scenarios/http_post_scenario.dart index 8e943ec..633e5d3 100644 --- a/features/fixture_resources/lib/scenarios/http_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_post_scenario.dart @@ -7,7 +7,7 @@ class HttpPostScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); http.addSubscriber(BugsnagPerformance.networkInstrumentation); http.BugSnagHttpClient() .post(FixtureConfig.MAZE_HOST, body: {"key": "value"}); diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart index 809cfce..2a15304 100644 --- a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -16,7 +16,7 @@ class InitialPScenario extends Scenario { BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); - setBatchSize(1); + setMaxBatchSize(1); BugsnagPerformance.startSpan('First').end(); } diff --git a/features/fixture_resources/lib/scenarios/make_current_context.dart b/features/fixture_resources/lib/scenarios/make_current_context.dart index 4b4c759..c199aa5 100644 --- a/features/fixture_resources/lib/scenarios/make_current_context.dart +++ b/features/fixture_resources/lib/scenarios/make_current_context.dart @@ -7,7 +7,7 @@ class MakeCurrentContextScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(3); + setMaxBatchSize(3); final span1 = BugsnagPerformance.startSpan('span1'); final span2 = BugsnagPerformance.startSpan('span2', makeCurrentContext: false); diff --git a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart index cf96af2..a37b505 100644 --- a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart @@ -5,7 +5,7 @@ class ManualSpanScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(1); + setMaxBatchSize(1); final span = BugsnagPerformance.startSpan('ManualSpanScenario'); span.end(); } diff --git a/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart b/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart new file mode 100644 index 0000000..36c78e8 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart @@ -0,0 +1,12 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class MaxBatchAgeScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchAge(5000); + final span = BugsnagPerformance.startSpan('MaxBatchAgeScenario'); + span.end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart index 28f6e74..dddd527 100644 --- a/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart +++ b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart @@ -7,7 +7,7 @@ class NewZoneNewContextScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(4); + setMaxBatchSize(4); final span1 = BugsnagPerformance.startSpan('span1'); runZoned(() { diff --git a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart index 92f73ac..e357ff5 100644 --- a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart +++ b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart @@ -7,7 +7,7 @@ class PassContextToNewZoneScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(3); + setMaxBatchSize(3); final span1 = BugsnagPerformance.startSpan('span1'); runZoned(() { final span2 = BugsnagPerformance.startSpan('span2', parentContext: span1); diff --git a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart index 32704ce..94ebe7d 100644 --- a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart +++ b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart @@ -15,7 +15,7 @@ class ProbabilityExpiryScenario extends Scenario { BugsnagPerformance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); - setBatchSize(1); + setMaxBatchSize(1); await Future.delayed(Duration(milliseconds: 500)); final span = BugsnagPerformance.startSpan('myspan'); span.end(); diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 594d5e0..78a4914 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -18,8 +18,12 @@ abstract class Scenario { Future run(); - void setBatchSize(int size) { - BugsnagPerformance.setExtraConfig("autoTriggerExportOnBatchSize", size); + void setMaxBatchSize(int size) { + BugsnagPerformance.setExtraConfig("maxBatchSize", size); + } + + void setMaxBatchAge(int milliseconds) { + BugsnagPerformance.setExtraConfig("maxBatchAge", milliseconds); } Future startBugsnag() async { diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 2a1f1bd..1b5fb8d 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -18,6 +18,7 @@ import 'http_callback_cancel_span.dart'; import 'dio_get_scenario.dart'; import 'dio_post_scenario.dart'; import 'http_get_multiple_subscribers_scenario.dart'; +import 'max_batch_age_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -52,5 +53,7 @@ final List> scenarios = [ ScenarioInfo('HttpGetMultipleSubscribersScenario', () => HttpGetMultipleSubscribersScenario()), ScenarioInfo('AutoInstrumentNavigationBasicScenario', - () => AutoInstrumentNavigationBasicScenario()) + () => AutoInstrumentNavigationBasicScenario()), + ScenarioInfo('MaxBatchAgeScenario', + () => MaxBatchAgeScenario()), ]; diff --git a/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart index d962dc5..c0aa08f 100644 --- a/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart @@ -5,7 +5,7 @@ class SimpleNestedSpanScenario extends Scenario { @override Future run() async { await startBugsnag(); - setBatchSize(2); + setMaxBatchSize(2); final span1 = BugsnagPerformance.startSpan('span1'); final span2 = BugsnagPerformance.startSpan('span2'); span2.end(); diff --git a/features/manual_span.feature b/features/manual_span.feature index fdd8903..b17e751 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -13,4 +13,12 @@ Feature: Manual Spans * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span bool attribute "bugsnag.span.first_class" is true * every span string attribute "bugsnag.span.category" equals "custom" - * a span double attribute "bugsnag.sampling.p" equals 1.0 \ No newline at end of file + * a span double attribute "bugsnag.sampling.p" equals 1.0 + + Scenario: Max Batch Age + When I run "MaxBatchAgeScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span field "name" equals "MaxBatchAgeScenario" diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index fc5f082..56c97b1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -2,7 +2,8 @@ class BugsnagPerformanceConfiguration { BugsnagPerformanceConfiguration({this.apiKey, this.endpoint}); String? apiKey; Uri? endpoint; - int autoTriggerExportOnBatchSize = 100; + int maxBatchSize = 100; + int maxBatchAge = 60 * 1000; // milliseconds int probabilityRequestsPause = 30000; int probabilityValueExpireTime = 24 * 3600 * 1000; bool instrumentAppStart = true; @@ -10,8 +11,8 @@ class BugsnagPerformanceConfiguration { void applyExtraConfig(String key, dynamic value) { switch (key) { - case 'autoTriggerExportOnBatchSize': - autoTriggerExportOnBatchSize = value; + case 'maxBatchSize': + maxBatchSize = value; break; case 'probabilityRequestsPause': probabilityRequestsPause = value; @@ -25,6 +26,9 @@ class BugsnagPerformanceConfiguration { case 'instrumentNavigation': instrumentNavigation = value; break; + case 'maxBatchAge': + maxBatchAge = value; + break; } } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart index 36a4d96..4dfb3a4 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/span_batch.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:bugsnag_flutter_performance/src/configuration.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; @@ -12,15 +14,21 @@ abstract class SpanBatch { class SpanBatchImpl implements SpanBatch { List _spans = []; - int _autoTriggerExportOnBatchSize = 0; + int _maxBatchSize = 0; + int _maxBatchAge = 0; bool _drainIsAllowed = false; + final DateTime _creationTime = DateTime.now(); @override void Function(SpanBatch batch) onBatchFull = (_) {}; @override void configure(BugsnagPerformanceConfiguration configuration) { - _autoTriggerExportOnBatchSize = configuration.autoTriggerExportOnBatchSize; + _maxBatchSize = configuration.maxBatchSize; + _maxBatchAge = configuration.maxBatchAge; + Timer.periodic(const Duration(milliseconds: 500), (timer) { + _checkForMaxBatchAge(); + }); } @override @@ -64,5 +72,13 @@ class SpanBatchImpl implements SpanBatch { } } - bool get _isFull => _spans.length >= _autoTriggerExportOnBatchSize; + bool get _isFull => _spans.length >= _maxBatchSize; + + void _checkForMaxBatchAge() { + if (DateTime.now().difference(_creationTime).inMilliseconds > + _maxBatchAge) { + _drainIsAllowed = true; + onBatchFull(this); + } + } } diff --git a/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart b/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart index ade093d..0106887 100644 --- a/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/uploader/span_batch_test.dart @@ -32,8 +32,7 @@ void main() { setUp(() { batch.configure( - BugsnagPerformanceConfiguration() - ..autoTriggerExportOnBatchSize = batchSize, + BugsnagPerformanceConfiguration()..maxBatchSize = batchSize, ); }); From 255a4b0b9dd7b4162a5700a140d21f2b04eb204e Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 13 Mar 2024 09:41:38 +0100 Subject: [PATCH 38/66] PLAT-10199 create dart io wrapper (#37) --- .gitmodules | 3 +++ .../lib/scenarios/dart_io_get_scenario.dart | 18 ++++++++++++++++++ .../lib/scenarios/scenarios.dart | 2 ++ features/network_spans.feature | 15 +++++++++++++++ features/scripts/generate_fixture.sh | 6 ++++++ packages/bugsnag-flutter-dart-io-http-client | 1 + 6 files changed, 45 insertions(+) create mode 100644 features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart create mode 160000 packages/bugsnag-flutter-dart-io-http-client diff --git a/.gitmodules b/.gitmodules index cf17a85..ecfb18d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "packages/bugsnag-flutter-navigator-observer"] path = packages/bugsnag-flutter-navigator-observer url=git@github.com:bugsnag/bugsnag-flutter-navigator-observer.git +[submodule "packages/bugsnag-flutter-dart-io-http-client"] + path = packages/bugsnag-flutter-dart-io-http-client + url = git@github.com:bugsnag/bugsnag-flutter-dart-io-http-client.git diff --git a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart new file mode 100644 index 0000000..d098303 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart @@ -0,0 +1,18 @@ +import 'dart:io'; + +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dartIo; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import '../main.dart'; +import 'scenario.dart'; + +class DartIoGetScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(1); + dartIo.addSubscriber(BugsnagPerformance.networkInstrumentation); + final client = dartIo.BugsnagHttpClient(); + HttpClientRequest request = await client.getUrl(FixtureConfig.MAZE_HOST); + await request.close(); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 1b5fb8d..37d3e23 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -19,6 +19,7 @@ import 'dio_get_scenario.dart'; import 'dio_post_scenario.dart'; import 'http_get_multiple_subscribers_scenario.dart'; import 'max_batch_age_scenario.dart'; +import 'dart_io_get_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -56,4 +57,5 @@ final List> scenarios = [ () => AutoInstrumentNavigationBasicScenario()), ScenarioInfo('MaxBatchAgeScenario', () => MaxBatchAgeScenario()), + ScenarioInfo('DartIoGetScenario', () => DartIoGetScenario()), ]; diff --git a/features/network_spans.feature b/features/network_spans.feature index 17c6ca6..8ed0999 100644 --- a/features/network_spans.feature +++ b/features/network_spans.feature @@ -121,4 +121,19 @@ Feature: Network Spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "DIOCallbackCancelSpanScenario" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "custom" + #DART IO WRAPPER + Scenario: DIO Get + When I run "DartIoGetScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "HTTP/GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.span.category" equals "network" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.method" equals "GET" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "http.url" matches the regex "^http:\/\/\S*:\d{4}(\/.*)?" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + \ No newline at end of file diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index e2f2e97..e7822dd 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -14,6 +14,9 @@ HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" DIO_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-dio-client" +DART_IO_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-dart-io-http-client" + + EXPORT_OPTIONS=features/fixture_resources/exportOptions.plist @@ -60,6 +63,9 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_http_client:{'path $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_dio_client:{'path':'$DIO_WRAPPER_PACKAGE_PATH'}" +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_flutter_dart_io_http_client:{'path':'$DART_IO_WRAPPER_PACKAGE_PATH'}" + + echo "update min sdk version in android gradle file" sed -i '' 's/minSdkVersion flutter.minSdkVersion/minSdkVersion 19/g' "$ANDROID_GRADLE" diff --git a/packages/bugsnag-flutter-dart-io-http-client b/packages/bugsnag-flutter-dart-io-http-client new file mode 160000 index 0000000..a3ea6f1 --- /dev/null +++ b/packages/bugsnag-flutter-dart-io-http-client @@ -0,0 +1 @@ +Subproject commit a3ea6f1f0bf32080f57fe54391469153456ebe16 From 0f9e06d9d2a24e666be035798084c791d7463f87 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 14 Mar 2024 16:22:03 +0100 Subject: [PATCH 39/66] PLAT-11740 refactor span attributes (#39) * initial refactor * changed to setters and getters and fixed unit test * review update --- .../src/bugsnag_performance_public_api.dart | 2 +- .../app_start_instrumentation.dart | 3 +- .../lib/src/span.dart | 4 +- .../lib/src/span_attributes.dart | 253 ++++++------------ .../lib/src/uploader/package_builder.dart | 14 +- .../test/src/span_test.dart | 10 + 6 files changed, 109 insertions(+), 177 deletions(-) diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 7b2e795..443c038 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -30,7 +30,7 @@ class BugsnagPerformance { startTime: startTime, parentContext: parentContext, makeCurrentContext: makeCurrentContext, - attributes: BugsnagPerformanceSpanAttributes(isFirstClass: true)); + attributes: BugsnagPerformanceSpanAttributes(category: "custom", isFirstClass: true)); } static BugsnagPerformanceSpan startNetworkSpan( diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart index 96d6f10..7b2f5bc 100644 --- a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart @@ -31,7 +31,8 @@ class AppStartInstrumentationImpl implements AppStartInstrumentation { } flutterInitSpan = client.startSpan( '[AppStart/FlutterInit]', - attributes: BugsnagPerformanceSpanAttributes(category: 'app_start', appStartType: 'FlutterInit'), + attributes: BugsnagPerformanceSpanAttributes( + category: 'app_start', appStartType: 'FlutterInit'), ); preRunAppPhaseSpan = client.startSpan( '[AppStartPhase/pre runApp()]', diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 8cfb3c3..8914421 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -118,7 +118,9 @@ class BugsnagPerformanceSpanImpl int get hashCode => toJson().hashCode; void updateSamplingProbability(double samplingProbability) { - if (samplingProbability < attributes.samplingProbability) { + double? currentSamplingProbability = attributes.samplingProbability; + if (currentSamplingProbability == null || + samplingProbability < currentSamplingProbability) { attributes.samplingProbability = samplingProbability; } } diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index edb905a..d487856 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -1,178 +1,95 @@ class BugsnagPerformanceSpanAttributes { - BugsnagPerformanceSpanAttributes( - {this.category = 'custom', - this.isFirstClass, - this.samplingProbability = 1.0, - this.phase, - this.url, - this.httpMethod, - this.httpStatusCode, - this.requestContentLength, - this.responseContentLength, - this.appStartType}); + final Map attributes = {}; - final String category; - final bool? isFirstClass; - double samplingProbability; - final String? phase; - String? url; - String? httpMethod; - int? httpStatusCode; - int? requestContentLength; - int? responseContentLength; - final String? appStartType; + BugsnagPerformanceSpanAttributes({ + String? category, + String? httpMethod, + String? url, + bool? isFirstClass, + double? samplingProbability, + String? phase, + String? appStartType, + Map? additionalAttributes, + }) { + setAttribute('bugsnag.span.category', category); + setAttribute('bugsnag.span.first_class', isFirstClass); + setAttribute('bugsnag.sampling.p', samplingProbability); + setAttribute('bugsnag.phase', phase); + setAttribute('bugsnag.app_start.type', appStartType); + setAttribute('http.method', httpMethod); + setAttribute('http.url', url); + additionalAttributes?.forEach((key, value) {setAttribute(key, value);}); + } - BugsnagPerformanceSpanAttributes.fromJson(dynamic json) - : category = _value( - json: json, - key: 'bugsnag.span.category', - type: _ParameterType.string, - ) as String? ?? - 'custom', - isFirstClass = _value( - json: json, - key: 'bugsnag.span.first_class', - type: _ParameterType.bool, - ) as bool?, - samplingProbability = _value( - json: json, - key: 'bugsnag.sampling.p', - type: _ParameterType.double, - ) as double? ?? - 1.0, - phase = _value( - json: json, - key: 'bugsnag.phase', - type: _ParameterType.string, - ) as String?, - appStartType = _value( - json: json, - key: 'bugsnag.app_start.type', - type: _ParameterType.string, - ) as String?, - url = _value( - json: json, - key: 'http.url', - type: _ParameterType.string, - ) as String?, - httpMethod = _value( - json: json, - key: 'http.method', - type: _ParameterType.string, - ) as String?, - httpStatusCode = _value( - json: json, - key: 'http.status_code', - type: _ParameterType.double, - ) as int?, - requestContentLength = _value( - json: json, - key: 'http.request_content_length', - type: _ParameterType.double, - ) as int?, - responseContentLength = _value( - json: json, - key: 'http.response_content_length', - type: _ParameterType.double, - ) as int?; + void setAttribute(String key, dynamic value) { + if (value != null) { + attributes[key] = value; + } + } - dynamic toJson() => [ - { - 'key': 'bugsnag.span.category', - 'value': { - 'stringValue': category, - }, - }, - if (isFirstClass != null) - { - 'key': 'bugsnag.span.first_class', - 'value': { - 'boolValue': isFirstClass, - }, - }, - { - 'key': 'bugsnag.sampling.p', - 'value': { - 'doubleValue': samplingProbability, - }, - }, - if (phase != null) - { - 'key': 'bugsnag.phase', - 'value': { - 'stringValue': phase, - } - }, - if (appStartType != null) - { - 'key': 'bugsnag.app_start.type', - 'value': { - 'stringValue': appStartType, - } - }, - if (url != null) - { - 'key': 'http.url', - 'value': { - 'stringValue': url, - } - }, - if (httpMethod != null) - { - 'key': 'http.method', - 'value': { - 'stringValue': httpMethod, - } - }, - if (httpStatusCode != null) - { - 'key': 'http.status_code', - 'value': { - 'intValue': - httpStatusCode.toString(), //integerValue should be a string - } - }, - if (requestContentLength != null && requestContentLength != 0) - { - 'key': 'http.request_content_length', - 'value': { - 'intValue': requestContentLength - .toString(), //integerValue should be a string - } - }, - if (responseContentLength != null && responseContentLength != 0) - { - 'key': 'http.response_content_length', - 'value': { - 'intValue': responseContentLength - .toString(), //integerValue should be a string - } - } - ]; -} + set httpStatusCode(int httpStatusCode) { + setAttribute('http.status_code', httpStatusCode); + } -enum _ParameterType { string, double, bool } + set requestContentLength(int requestContentLength) { + setAttribute('http.request_content_length', requestContentLength); + } + + set responseContentLength(int responseContentLength) { + setAttribute('http.response_content_length', responseContentLength); + } -dynamic _value({ - required dynamic json, - required String key, - required _ParameterType type, -}) { - final attributes = json as List>?; - if (attributes == null) { - return null; + set samplingProbability(double? samplingProbability) { + setAttribute('bugsnag.sampling.p', samplingProbability); } - final entry = - attributes.where((element) => element['key'] == key).firstOrNull; - if (entry == null) { - return null; + + double? get samplingProbability { + return attributes['bugsnag.sampling.p']; } - switch (type) { - case _ParameterType.string: - return entry['value']['stringValue']; - case _ParameterType.double: - return entry['value']['doubleValue']; - case _ParameterType.bool: - return entry['value']['boolValue']; + + dynamic toJson() { + const typeMap = { + bool: 'boolValue', + double: 'doubleValue', + int: 'intValue', + String: 'stringValue', + }; + + return attributes.entries.map((entry) { + final key = entry.key; + final value = entry.value; + String valueType = typeMap[value.runtimeType] ?? 'stringValue'; + dynamic formattedValue = value is int ? value.toString() : value; + + return { + 'key': key, + 'value': {valueType: formattedValue}, + }; + }).toList(); + } + + factory BugsnagPerformanceSpanAttributes.fromJson( + List> json) { + final attributes = {}; + for (var element in json) { + final key = element['key']; + final Map valueMap = element['value']; + dynamic value; + + if (valueMap.containsKey('stringValue')) { + value = valueMap['stringValue']; + } else if (valueMap.containsKey('boolValue')) { + value = valueMap['boolValue']; + } else if (valueMap.containsKey('doubleValue')) { + value = double.tryParse(valueMap['doubleValue'].toString()); + } else if (valueMap.containsKey('intValue')) { + value = int.tryParse(valueMap['intValue']); + } + + if (key != null && value != null) { + attributes[key] = value; + } + } + return BugsnagPerformanceSpanAttributes(additionalAttributes: attributes); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index c96b5f1..d025845 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -102,14 +102,16 @@ class PackageBuilderImpl implements PackageBuilder { required List spans, }) { Map spansWithProbability = {}; - for (var element in spans) { - if (element is BugsnagPerformanceSpanImpl) { - final spansCount = - spansWithProbability[element.attributes.samplingProbability] ?? 0; - spansWithProbability[element.attributes.samplingProbability] = - spansCount + 1; + for (var span in spans) { + if (span is BugsnagPerformanceSpanImpl) { + final samplingProbability = span.attributes.samplingProbability; + if (samplingProbability != null) { + final spansCount = spansWithProbability[samplingProbability] ?? 0; + spansWithProbability[samplingProbability] = spansCount + 1; + } } } + return spansWithProbability.entries .map((e) => '${e.key.toStringAsFixed(2).replaceFirst('0.', '.').replaceAll(RegExp(r"([.]*0+)(?!.*\d)"), "")}:${e.value}') diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 12a88c4..9005b7e 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -122,6 +122,11 @@ void main() { 'traceId': 'ffa74cc50baa432515e9b28fc4abf2cb', 'spanId': 'fa0e2d25f149f215', 'parentSpanId': '6293f00f47da54de', + 'attributes': [{ + 'key': 'custom', + 'value': {'stringValue': 'value'} + }, + ], }; final span = BugsnagPerformanceSpanImpl.fromJson(json); expect(span.name, equals(name)); @@ -152,6 +157,11 @@ void main() { isUtc: true) .nanosecondsSinceEpoch .toString(), + 'attributes': [{ + 'key': 'custom', + 'value': {'stringValue': 'value'} + }, + ], }; final span = BugsnagPerformanceSpanImpl.fromJson(json); expect(span.name, equals(name)); From 5e5c2c1ed83d6973fcd30b393057b141b8d02d45 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 20 Mar 2024 11:08:11 +0100 Subject: [PATCH 40/66] flush on successful delivery --- packages/bugsnag_flutter_performance/lib/src/client.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index dd05324..8fdf039 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -202,6 +202,8 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final result = await _uploader?.upload(package: package); if (result == RequestResult.retriableFailure) { _retryQueue?.enqueue(headers: package.headers, body: package.payload); + }else if(result == RequestResult.success){ + _retryQueue?.flush(); } } From 78c18466945caaa4390b1732443ac40ab786964e Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 21 Mar 2024 09:28:31 +0000 Subject: [PATCH 41/66] Avoid possible span context stack memory leaks (#40) * fix(context): replaced the map of zone ids with an `Expando` on the Zone, avoiding possible memory leaks * test(context): introduced unit tests for the span context behaviour * test(e2e): reworked `PassContextToNewZoneScenario` to match expected zone passing behaviour --- .../lib/scenarios/pass_context_scenario.dart | 2 +- features/nested_spans.feature | 2 +- .../lib/src/client.dart | 40 ++++++------ .../test/src/span_context_test.dart | 61 +++++++++++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 packages/bugsnag_flutter_performance/test/src/span_context_test.dart diff --git a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart index e357ff5..2121524 100644 --- a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart +++ b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart @@ -11,8 +11,8 @@ class PassContextToNewZoneScenario extends Scenario { final span1 = BugsnagPerformance.startSpan('span1'); runZoned(() { final span2 = BugsnagPerformance.startSpan('span2', parentContext: span1); - span2.end(); final span3 = BugsnagPerformance.startSpan('span3'); + span2.end(); span3.end(); }, zoneValues: {}); await Future.delayed(const Duration(milliseconds: 1000)); diff --git a/features/nested_spans.feature b/features/nested_spans.feature index 094b979..1baac9f 100644 --- a/features/nested_spans.feature +++ b/features/nested_spans.feature @@ -32,7 +32,7 @@ Feature: Nested Spans * the span named "span1" has no parent * the span named "span1" is the parent of the span named "span2" - * the span named "span1" is the parent of the span named "span3" + * the span named "span2" is the parent of the span named "span3" Scenario: Make Current Context False When I run "MakeCurrentContextScenario" diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 8fdf039..7632d3e 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -16,11 +16,11 @@ import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; -import 'package:flutter/widgets.dart'; -import 'bugsnag_network_request_info.dart'; // ignore: implementation_imports import 'package:bugsnag_navigator_observer/src/bugsnag_navigator_observer_callbacks.dart'; +import 'package:flutter/widgets.dart'; +import 'bugsnag_network_request_info.dart'; import 'configuration.dart'; import 'span.dart'; @@ -34,6 +34,7 @@ abstract class BugsnagPerformanceClient { networkRequestCallback}); Future measureRunApp(FutureOr Function() runApp); + BugsnagPerformanceSpan startSpan( String name, { DateTime? startTime, @@ -41,7 +42,9 @@ abstract class BugsnagPerformanceClient { bool? makeCurrentContext = true, BugsnagPerformanceSpanAttributes? attributes, }); + BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod); + dynamic networkInstrumentation(dynamic); } @@ -60,11 +63,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { late final SamplingProbabilityStore _probabilityStore; late final AppStartInstrumentation _appStartInstrumentation; late final NavigationInstrumentation _navigationInstrumentation; - final Map _zoneContextStacks = {}; final Map _networkSpans = {}; BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? _networkRequestCallback; final Map _potentiallyOpenSpans = {}; + final spanContextStackExpando = Expando(); BugsnagPerformanceClientImpl({BugsnagLifecycleListener? lifecycleListener}) { retryQueueBuilder = RetryQueueBuilderImpl(); @@ -117,11 +120,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { bool? makeCurrentContext = true, BugsnagPerformanceSpanAttributes? attributes, }) { - if (parentContext != null) { - _addContext(parentContext); - } - - final parent = parentContext ?? _getCurrentContext(); + final parent = parentContext ?? getCurrentContext(); final span = BugsnagPerformanceSpanImpl( name: name, @@ -133,10 +132,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } _potentiallyOpenSpans.remove(endedSpan.spanId); }, - onCanceled: (cancledSpan) { - _potentiallyOpenSpans.remove(cancledSpan.spanId); + onCanceled: (canceledSpan) { + _potentiallyOpenSpans.remove(canceledSpan.spanId); }, parentSpanId: parent?.spanId, + traceId: parent?.traceId, attributes: attributes, ); span.clock = _clock; @@ -202,7 +202,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { final result = await _uploader?.upload(package: package); if (result == RequestResult.retriableFailure) { _retryQueue?.enqueue(headers: package.headers, body: package.payload); - }else if(result == RequestResult.success){ + } else if (result == RequestResult.success) { _retryQueue?.flush(); } } @@ -248,23 +248,23 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } BugsnagPerformanceSpanContextStack? _getContextStack() { - if (_zoneContextStacks.containsKey(Zone.current.hashCode)) { - return _zoneContextStacks[Zone.current.hashCode]; - } else { - _zoneContextStacks[Zone.current.hashCode] = - BugsnagPerformanceSpanContextStackImpl(); - return _zoneContextStacks[Zone.current.hashCode]; + var stack = spanContextStackExpando[Zone.current]; + if (stack == null) { + stack = BugsnagPerformanceSpanContextStackImpl(); + spanContextStackExpando[Zone.current] = stack; } + + return stack; } void _addContext(BugsnagPerformanceSpanContext newContext) { var stack = _getContextStack(); - if (stack?.getCurrentContext() != newContext) { - _getContextStack()?.pushContext(newContext); + if (stack != null && stack.getCurrentContext() != newContext) { + stack.pushContext(newContext); } } - BugsnagPerformanceSpanContext? _getCurrentContext() { + BugsnagPerformanceSpanContext? getCurrentContext() { return _getContextStack()?.getCurrentContext(); } diff --git a/packages/bugsnag_flutter_performance/test/src/span_context_test.dart b/packages/bugsnag_flutter_performance/test/src/span_context_test.dart new file mode 100644 index 0000000..38310e1 --- /dev/null +++ b/packages/bugsnag_flutter_performance/test/src/span_context_test.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'client_test.dart'; + +void main() { + group('BugsnagPerformanceClientImpl Span Context', () { + late BugsnagPerformanceClientImpl client; + final lifecycleListener = MockLifecycleListener(); + setUp(() { + client = + BugsnagPerformanceClientImpl(lifecycleListener: lifecycleListener); + client.retryQueueBuilder = MockRetryQueueBuilder(); + }); + + test('simple span context parentage', () { + final span1 = client.startSpan('span1'); + final span2 = client.startSpan('span2'); + span1.end(); + span2.end(); + + expect(span1.parentSpanId, isNull, reason: 'span1 should have no parent'); + expect(span2.parentSpanId, equals(span1.spanId), + reason: 'span1 is parent of span2'); + expect(span1.traceId, equals(span2.traceId), + reason: 'trace ids should match'); + }); + + test('span context in nested zone', () { + final outside = client.startSpan('outside span'); + + final [nestedOpen, nestedClosed] = Zone.current.fork().run(() { + final span2 = client.startSpan('inside span'); + final span3 = client.startSpan('very nested span'); + span3.end(); + + return [span2, span3]; + }); + + final outsideNested = client.startSpan('outside but nested'); + outsideNested.end(); + nestedOpen.end(); + outside.end(); + + expect(outside.parentSpanId, isNull, + reason: 'root span should have no parent'); + expect(nestedOpen.parentSpanId, isNull, + reason: 'zoned root should have no parent'); + expect(nestedClosed.parentSpanId, nestedOpen.spanId, + reason: 'zoned child should have zoned root as parent'); + expect(outsideNested.parentSpanId, outside.spanId, + reason: 'outside child should have outside root as parent'); + expect(outside.traceId, outsideNested.traceId, + reason: 'outside spans should share traceId'); + expect(nestedOpen.traceId, nestedClosed.traceId, + reason: 'zoned spans should share traceId'); + }); + }); +} From 4cce724dd69956ce800e4053de68972b457d0099 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 25 Mar 2024 09:34:28 +0100 Subject: [PATCH 42/66] UAT configuration additions (#41) --- .../custom_app_version_scenario.dart | 11 +++++ ...custom_enabled_release_stage_scenario.dart | 14 +++++++ .../custom_release_stage_scenario.dart | 11 +++++ .../lib/scenarios/dart_io_get_scenario.dart | 3 +- .../scenarios/dio_callback_cancel_span.dart | 2 +- ...disable_custom_release_stage_scenario.dart | 14 +++++++ .../lib/scenarios/initial_p_scenario.dart | 4 +- ...nual_span_isfirstclass_false_scenario.dart | 14 +++++++ .../lib/scenarios/manual_span_scenario.dart | 3 +- .../lib/scenarios/max_batch_age_scenario.dart | 3 +- .../probability_expiry_scenario.dart | 3 +- .../lib/scenarios/scenario.dart | 18 ++++++-- .../lib/scenarios/scenarios.dart | 17 +++++++- features/manual_span.feature | 7 ++++ features/resource_attributes.feature | 31 ++++++++++++++ .../src/bugsnag_performance_public_api.dart | 42 ++++++++++++------- .../lib/src/client.dart | 41 +++++++++++++----- .../lib/src/configuration.dart | 16 ++++++- .../src/extensions/resource_attributes.dart | 22 +++++----- .../lib/src/span_attributes.dart | 4 +- .../lib/src/uploader/package_builder.dart | 11 ++++- .../test/src/span_test.dart | 12 +++--- 22 files changed, 244 insertions(+), 59 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/custom_app_version_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/custom_enabled_release_stage_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/custom_release_stage_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/disable_custom_release_stage_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/custom_app_version_scenario.dart b/features/fixture_resources/lib/scenarios/custom_app_version_scenario.dart new file mode 100644 index 0000000..e4c3874 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/custom_app_version_scenario.dart @@ -0,0 +1,11 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class CustomAppVersionScenario extends Scenario { + @override + Future run() async { + await startBugsnag(appVersion: "999.888.777"); + setMaxBatchSize(1); + doSimpleSpan('CustomAppVersionScenario'); + } +} diff --git a/features/fixture_resources/lib/scenarios/custom_enabled_release_stage_scenario.dart b/features/fixture_resources/lib/scenarios/custom_enabled_release_stage_scenario.dart new file mode 100644 index 0000000..c9677c3 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/custom_enabled_release_stage_scenario.dart @@ -0,0 +1,14 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class CustomEnabledReleaseStageScenario extends Scenario { + @override + Future run() async { + await startBugsnag( + releaseStage: "CustomEnabledReleaseStageScenario", + enabledReleaseStages: ["CustomEnabledReleaseStageScenario"], + ); + setMaxBatchSize(1); + doSimpleSpan('CustomEnabledReleaseStageScenario'); + } +} diff --git a/features/fixture_resources/lib/scenarios/custom_release_stage_scenario.dart b/features/fixture_resources/lib/scenarios/custom_release_stage_scenario.dart new file mode 100644 index 0000000..6a521c9 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/custom_release_stage_scenario.dart @@ -0,0 +1,11 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class CustomReleaseStageScenario extends Scenario { + @override + Future run() async { + await startBugsnag(releaseStage: "CustomReleaseStageScenario"); + setMaxBatchSize(1); + doSimpleSpan('CustomReleaseStageScenario'); + } +} diff --git a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart index d098303..3b0a59a 100644 --- a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart @@ -1,6 +1,7 @@ import 'dart:io'; -import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dartIo; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' + as dartIo; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import '../main.dart'; import 'scenario.dart'; diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart index b1fa9c0..3f0ff39 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -20,6 +20,6 @@ class DIOCallbackCancelSpanScenario extends Scenario { .client .get(FixtureConfig.MAZE_HOST.toString()); await Future.delayed(const Duration(seconds: 10)); - BugsnagPerformance.startSpan('DIOCallbackCancelSpanScenario').end(); + doSimpleSpan('DIOCallbackCancelSpanScenario'); } } diff --git a/features/fixture_resources/lib/scenarios/disable_custom_release_stage_scenario.dart b/features/fixture_resources/lib/scenarios/disable_custom_release_stage_scenario.dart new file mode 100644 index 0000000..da25733 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/disable_custom_release_stage_scenario.dart @@ -0,0 +1,14 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class DisableCustomReleaseStageScenario extends Scenario { + @override + Future run() async { + await startBugsnag( + releaseStage: "custom", + enabledReleaseStages: ["release"], + ); + setMaxBatchSize(1); + doSimpleSpan('DisableCustomReleaseStageScenario'); + } +} diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart index 2a15304..349604c 100644 --- a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -17,11 +17,11 @@ class InitialPScenario extends Scenario { apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); setMaxBatchSize(1); - BugsnagPerformance.startSpan('First').end(); + doSimpleSpan('First'); } void step2() { - BugsnagPerformance.startSpan('Second').end(); + doSimpleSpan('Second'); } @override diff --git a/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart new file mode 100644 index 0000000..6004797 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart @@ -0,0 +1,14 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class ManualSpanIsFirstClassFalseScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(1); + BugsnagPerformance.startSpan( + 'ManualSpanIsFirstClassFalseScenario', + isFirstClass: false, + ).end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart index a37b505..e1774b8 100644 --- a/features/fixture_resources/lib/scenarios/manual_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_span_scenario.dart @@ -6,7 +6,6 @@ class ManualSpanScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - final span = BugsnagPerformance.startSpan('ManualSpanScenario'); - span.end(); + doSimpleSpan('ManualSpanScenario'); } } diff --git a/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart b/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart index 36c78e8..4ecc680 100644 --- a/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart +++ b/features/fixture_resources/lib/scenarios/max_batch_age_scenario.dart @@ -6,7 +6,6 @@ class MaxBatchAgeScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchAge(5000); - final span = BugsnagPerformance.startSpan('MaxBatchAgeScenario'); - span.end(); + doSimpleSpan('MaxBatchAgeScenario'); } } diff --git a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart index 94ebe7d..5e35bfa 100644 --- a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart +++ b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart @@ -17,7 +17,6 @@ class ProbabilityExpiryScenario extends Scenario { endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); setMaxBatchSize(1); await Future.delayed(Duration(milliseconds: 500)); - final span = BugsnagPerformance.startSpan('myspan'); - span.end(); + doSimpleSpan('myspan'); } } diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 78a4914..c8c5f46 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -18,6 +18,10 @@ abstract class Scenario { Future run(); + void doSimpleSpan(String name) { + BugsnagPerformance.startSpan(name).end(); + } + void setMaxBatchSize(int size) { BugsnagPerformance.setExtraConfig("maxBatchSize", size); } @@ -26,11 +30,19 @@ abstract class Scenario { BugsnagPerformance.setExtraConfig("maxBatchAge", milliseconds); } - Future startBugsnag() async { + Future startBugsnag({ + String? releaseStage, + List? enabledReleaseStages, + String? appVersion, + }) async { BugsnagPerformance.setExtraConfig("instrumentAppStart", false); await BugsnagPerformance.start( - apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces')); + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + releaseStage: releaseStage, + enabledReleaseStages: enabledReleaseStages, + appVersion: appVersion, + ); } void invokeMethod(String name) {} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 37d3e23..452ece2 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -20,6 +20,11 @@ import 'dio_post_scenario.dart'; import 'http_get_multiple_subscribers_scenario.dart'; import 'max_batch_age_scenario.dart'; import 'dart_io_get_scenario.dart'; +import 'custom_release_stage_scenario.dart'; +import 'custom_enabled_release_stage_scenario.dart'; +import 'disable_custom_release_stage_scenario.dart'; +import 'custom_app_version_scenario.dart'; +import 'manual_span_isfirstclass_false_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -55,7 +60,15 @@ final List> scenarios = [ () => HttpGetMultipleSubscribersScenario()), ScenarioInfo('AutoInstrumentNavigationBasicScenario', () => AutoInstrumentNavigationBasicScenario()), - ScenarioInfo('MaxBatchAgeScenario', - () => MaxBatchAgeScenario()), + ScenarioInfo('MaxBatchAgeScenario', () => MaxBatchAgeScenario()), ScenarioInfo('DartIoGetScenario', () => DartIoGetScenario()), + ScenarioInfo( + 'CustomReleaseStageScenario', () => CustomReleaseStageScenario()), + ScenarioInfo('CustomEnabledReleaseStageScenario', + () => CustomEnabledReleaseStageScenario()), + ScenarioInfo('DisableCustomReleaseStageScenario', + () => DisableCustomReleaseStageScenario()), + ScenarioInfo('CustomAppVersionScenario', () => CustomAppVersionScenario()), + ScenarioInfo('ManualSpanIsFirstClassFalseScenario', + () => ManualSpanIsFirstClassFalseScenario()), ]; diff --git a/features/manual_span.feature b/features/manual_span.feature index b17e751..fd01efd 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -15,6 +15,13 @@ Feature: Manual Spans * every span string attribute "bugsnag.span.category" equals "custom" * a span double attribute "bugsnag.sampling.p" equals 1.0 + Scenario: Manual Span isFirstClass false + When I run "ManualSpanIsFirstClassFalseScenario" + And I wait for 1 span + Then every span field "name" equals "ManualSpanIsFirstClassFalseScenario" + * every span bool attribute "bugsnag.span.first_class" is false + + Scenario: Max Batch Age When I run "MaxBatchAgeScenario" And I wait for 1 span diff --git a/features/resource_attributes.feature b/features/resource_attributes.feature index cd3585b..819a049 100644 --- a/features/resource_attributes.feature +++ b/features/resource_attributes.feature @@ -1,5 +1,36 @@ Feature: Resource Attributes + Scenario: Custom release stage + When I run "CustomReleaseStageScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span field "name" equals "CustomReleaseStageScenario" + * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "CustomReleaseStageScenario" + + Scenario: Custom enabled release stage + When I run "CustomEnabledReleaseStageScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span field "name" equals "CustomEnabledReleaseStageScenario" + * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "CustomEnabledReleaseStageScenario" + + Scenario: Custom app version + When I run "CustomAppVersionScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span field "name" equals "CustomAppVersionScenario" + * the trace payload field "resourceSpans.0.resource" string attribute "service.version" equals "999.888.777" + + Scenario: Disabled release stage + When I run "DisableCustomReleaseStageScenario" + * I should receive no traces + Scenario: Common Attributes When I run "ManualSpanScenario" And I wait for 1 span diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart index 443c038..1e0f35b 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart @@ -11,26 +11,40 @@ class BugsnagPerformance { static final BugsnagPerformanceClientImpl _client = BugsnagPerformanceClientImpl(); - static Future start( - {String? apiKey, - Uri? endpoint, - BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? - networkRequestCallback}) async { + static Future start({ + required String apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback, + String? releaseStage, + List? enabledReleaseStages, + String? appVersion, + }) async { return _client.start( - apiKey: apiKey, - endpoint: endpoint, - networkRequestCallback: networkRequestCallback); + apiKey: apiKey, + endpoint: endpoint, + networkRequestCallback: networkRequestCallback, + releaseStage: releaseStage, + enabledReleaseStages: enabledReleaseStages, + appVersion: appVersion, + ); } static BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime, BugsnagPerformanceSpanContext? parentContext, - bool? makeCurrentContext = true}) { - return _client.startSpan(name, - startTime: startTime, - parentContext: parentContext, - makeCurrentContext: makeCurrentContext, - attributes: BugsnagPerformanceSpanAttributes(category: "custom", isFirstClass: true)); + bool? makeCurrentContext = true, + bool? isFirstClass = true}) { + return _client.startSpan( + name, + startTime: startTime, + parentContext: parentContext, + makeCurrentContext: makeCurrentContext, + attributes: BugsnagPerformanceSpanAttributes( + category: "custom", + isFirstClass: isFirstClass, + ), + ); } static BugsnagPerformanceSpan startNetworkSpan( diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 7632d3e..21b68ce 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -27,11 +27,15 @@ import 'span.dart'; const _defaultEndpoint = 'https://otlp.bugsnag.com/v1/traces'; abstract class BugsnagPerformanceClient { - Future start( - {String? apiKey, - Uri? endpoint, - BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? - networkRequestCallback}); + Future start({ + String? apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback, + String? releaseStage, + List? enabledReleaseStages, + String? appVersion, + }); Future measureRunApp(FutureOr Function() runApp); @@ -88,17 +92,25 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } @override - Future start( - {String? apiKey, - Uri? endpoint, - BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? - networkRequestCallback}) async { + Future start({ + String? apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback, + String? releaseStage, + List? enabledReleaseStages, + String? appVersion, + }) async { WidgetsFlutterBinding.ensureInitialized(); _networkRequestCallback = networkRequestCallback; configuration = BugsnagPerformanceConfiguration( apiKey: apiKey, endpoint: endpoint ?? Uri.parse(_defaultEndpoint), + releaseStage: releaseStage ?? getDeploymentEnvironment(), + enabledReleaseStages: enabledReleaseStages, + appVersion: appVersion, ); + _packageBuilder.setConfig(configuration); _initialExtraConfig.forEach((key, value) { setExtraConfig(key, value); }); @@ -112,6 +124,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _lifecycleListener?.startObserving(onAppBackgrounded: _onAppBackgrounded); } + String getDeploymentEnvironment() { + final environment = Platform.environment['DEPLOYMENT_ENVIRONMENT']; + return environment ?? 'development'; + } + @override BugsnagPerformanceSpan startSpan( String name, { @@ -190,6 +207,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } void _sendBatch(SpanBatch batch) async { + if (!configuration!.releaseStageEnabled()) { + batch.drain(); + return; + } await _updateSamplingProbabilityIfNeeded(); var spans = batch.drain(); if (_sampler != null) { diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index 56c97b1..8bbaff2 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -1,5 +1,10 @@ class BugsnagPerformanceConfiguration { - BugsnagPerformanceConfiguration({this.apiKey, this.endpoint}); + BugsnagPerformanceConfiguration( + {this.apiKey, + this.endpoint, + this.releaseStage, + this.enabledReleaseStages, + this.appVersion}); String? apiKey; Uri? endpoint; int maxBatchSize = 100; @@ -8,6 +13,15 @@ class BugsnagPerformanceConfiguration { int probabilityValueExpireTime = 24 * 3600 * 1000; bool instrumentAppStart = true; bool instrumentNavigation = true; + String? releaseStage; + List? enabledReleaseStages; + String? appVersion; + + bool releaseStageEnabled() { + return releaseStage == null || + enabledReleaseStages == null || + enabledReleaseStages!.contains(releaseStage); + } void applyExtraConfig(String key, dynamic value) { switch (key) { diff --git a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart index ba6f316..5fbfce1 100644 --- a/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/extensions/resource_attributes.dart @@ -4,8 +4,11 @@ import 'package:package_info/package_info.dart'; import 'package:bugsnag_flutter_performance/src/device_id_manager.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; +import '../configuration.dart'; + abstract class ResourceAttributesProvider { - Future>> resourceAttributes(); + Future>> resourceAttributes( + BugsnagPerformanceConfiguration? config); } class ResourceAttributesProviderImpl implements ResourceAttributesProvider { @@ -14,9 +17,10 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { final DeviceIdManager _deviceIdManager = DeviceIdManagerImp(); @override - Future>> resourceAttributes() async { + Future>> resourceAttributes( + BugsnagPerformanceConfiguration? config) async { if (!_didInitializeAttributes) { - await _initializeResourceAttributes(); + await _initializeResourceAttributes(config); _didInitializeAttributes = true; } await _addNetworkStatus(); @@ -48,7 +52,8 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { } } - Future _initializeResourceAttributes() async { + Future _initializeResourceAttributes( + BugsnagPerformanceConfiguration? config) async { final deviceInfo = DeviceInfoPlugin(); final packageInfo = await PackageInfo.fromPlatform(); @@ -56,7 +61,7 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { { 'key': 'deployment.environment', 'value': { - 'stringValue': getDeploymentEnvironment(), + 'stringValue': config?.releaseStage, } }, { @@ -80,7 +85,7 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { { "key": "service.version", "value": { - "stringValue": packageInfo.version, + "stringValue": config?.appVersion ?? packageInfo.version, } }, { @@ -169,11 +174,6 @@ class ResourceAttributesProviderImpl implements ResourceAttributesProvider { return "Unknown"; } - static String getDeploymentEnvironment() { - final environment = Platform.environment['DEPLOYMENT_ENVIRONMENT']; - return environment ?? 'development'; - } - static Future getAndroidAPILevel(DeviceInfoPlugin deviceInfo) async { final androidInfo = await deviceInfo.androidInfo; return androidInfo.version.sdkInt.toString(); diff --git a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart index d487856..5330d5e 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_attributes.dart @@ -18,7 +18,9 @@ class BugsnagPerformanceSpanAttributes { setAttribute('bugsnag.app_start.type', appStartType); setAttribute('http.method', httpMethod); setAttribute('http.url', url); - additionalAttributes?.forEach((key, value) {setAttribute(key, value);}); + additionalAttributes?.forEach((key, value) { + setAttribute(key, value); + }); } void setAttribute(String key, dynamic value) { diff --git a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart index d025845..732f13c 100644 --- a/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart +++ b/packages/bugsnag_flutter_performance/lib/src/uploader/package_builder.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:bugsnag_flutter_performance/src/span.dart'; import 'package:bugsnag_flutter_performance/src/uploader/model/otlp_package.dart'; import 'package:crypto/crypto.dart'; +import '../configuration.dart'; import '../extensions/resource_attributes.dart'; const int _minSizeForGzip = 128; @@ -13,11 +14,12 @@ abstract class PackageBuilder { List spans, ); Future buildEmptyPackage(); + void setConfig(BugsnagPerformanceConfiguration? config); } class PackageBuilderImpl implements PackageBuilder { final ResourceAttributesProvider attributesProvider; - + BugsnagPerformanceConfiguration? _config; PackageBuilderImpl({ required this.attributesProvider, }); @@ -69,7 +71,7 @@ class PackageBuilderImpl implements PackageBuilder { } ], 'resource': { - 'attributes': await attributesProvider.resourceAttributes() + 'attributes': await attributesProvider.resourceAttributes(_config) }, } ] @@ -117,4 +119,9 @@ class PackageBuilderImpl implements PackageBuilder { '${e.key.toStringAsFixed(2).replaceFirst('0.', '.').replaceAll(RegExp(r"([.]*0+)(?!.*\d)"), "")}:${e.value}') .join(';'); } + + @override + void setConfig(BugsnagPerformanceConfiguration? config) { + _config = config; + } } diff --git a/packages/bugsnag_flutter_performance/test/src/span_test.dart b/packages/bugsnag_flutter_performance/test/src/span_test.dart index 9005b7e..8db1a5f 100644 --- a/packages/bugsnag_flutter_performance/test/src/span_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/span_test.dart @@ -122,7 +122,8 @@ void main() { 'traceId': 'ffa74cc50baa432515e9b28fc4abf2cb', 'spanId': 'fa0e2d25f149f215', 'parentSpanId': '6293f00f47da54de', - 'attributes': [{ + 'attributes': [ + { 'key': 'custom', 'value': {'stringValue': 'value'} }, @@ -157,10 +158,11 @@ void main() { isUtc: true) .nanosecondsSinceEpoch .toString(), - 'attributes': [{ - 'key': 'custom', - 'value': {'stringValue': 'value'} - }, + 'attributes': [ + { + 'key': 'custom', + 'value': {'stringValue': 'value'} + }, ], }; final span = BugsnagPerformanceSpanImpl.fromJson(json); From c698bb8904eaddfa49d82fce809664f77088cd31 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Mon, 25 Mar 2024 11:43:21 +0100 Subject: [PATCH 43/66] PLAT-11780 remove dio wrapper (#42) * remove sub mod * updated e2e tests * submod update --- .gitmodules | 3 --- .../scenarios/dio_callback_cancel_span.dart | 16 +++++++++++----- .../scenarios/dio_callback_edit_scenario.dart | 18 +++++++++++++----- .../lib/scenarios/dio_get_scenario.dart | 19 ++++++++++++++----- .../lib/scenarios/dio_post_scenario.dart | 18 +++++++++++++----- features/scripts/generate_fixture.sh | 5 +---- packages/bugsnag-flutter-dart-io-http-client | 2 +- packages/bugsnag-flutter-dio-client | 1 - 8 files changed, 53 insertions(+), 29 deletions(-) delete mode 160000 packages/bugsnag-flutter-dio-client diff --git a/.gitmodules b/.gitmodules index ecfb18d..71e2aeb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "packages/bugsnag-flutter-http-client"] path = packages/bugsnag-flutter-http-client url = git@github.com:bugsnag/bugsnag-flutter-http-client.git -[submodule "packages/bugsnag-flutter-dio-client"] - path = packages/bugsnag-flutter-dio-client - url = git@github.com:bugsnag/bugsnag-flutter-dio-client.git [submodule "packages/bugsnag-flutter-navigator-observer"] path = packages/bugsnag-flutter-navigator-observer url=git@github.com:bugsnag/bugsnag-flutter-navigator-observer.git diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart index 3f0ff39..abb8607 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -1,8 +1,10 @@ -import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:dio/dio.dart'; class DIOCallbackCancelSpanScenario extends Scenario { @override @@ -15,10 +17,14 @@ class DIOCallbackCancelSpanScenario extends Scenario { return null; }); setMaxBatchSize(1); - BugsnagDioClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .client - .get(FixtureConfig.MAZE_HOST.toString()); + dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + final dio = Dio(); + dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return dart_io.BugsnagHttpClient(); + }, + ); + dio.get(FixtureConfig.MAZE_HOST.toString()); await Future.delayed(const Duration(seconds: 10)); doSimpleSpan('DIOCallbackCancelSpanScenario'); } diff --git a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart index 3354344..8a77d5c 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart @@ -1,8 +1,10 @@ -import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:bugsnag_http_client/bugsnag_http_client.dart'; +import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:dio/dio.dart'; class DIOCallbackEditScenario extends Scenario { @override @@ -16,9 +18,15 @@ class DIOCallbackEditScenario extends Scenario { return info; }); setMaxBatchSize(1); - BugsnagDioClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .client - .get(FixtureConfig.MAZE_HOST.toString()); + + dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + final dio = Dio(); + dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return dart_io.BugsnagHttpClient(); + }, + ); + + dio.get(FixtureConfig.MAZE_HOST.toString()); } } diff --git a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart index eb15511..0ed3d62 100644 --- a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart @@ -1,5 +1,7 @@ -import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:dio/dio.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; @@ -8,9 +10,16 @@ class DIOGetScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - BugsnagDioClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .client - .get(FixtureConfig.MAZE_HOST.toString()); + + dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + final dio = Dio(); + dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return dart_io.BugsnagHttpClient(); + }, + ); + + dio.get(FixtureConfig.MAZE_HOST.toString()); + } } diff --git a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart index b1cfbda..e5f2c24 100644 --- a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart @@ -1,5 +1,7 @@ -import 'package:bugsnag_dio_client/bugsnag_dio_client.dart'; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:dio/dio.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; @@ -8,9 +10,15 @@ class DIOPostScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - BugsnagDioClient() - .withSubscriber(BugsnagPerformance.networkInstrumentation) - .client - .post(FixtureConfig.MAZE_HOST.toString(), data: {"key": "value"}); + + dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + final dio = Dio(); + dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return dart_io.BugsnagHttpClient(); + }, + ); + + dio.post(FixtureConfig.MAZE_HOST.toString(), data: {"key": "value"}); } } diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index e7822dd..3cebd76 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -12,8 +12,6 @@ NAVIGATOR_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-navigator-observer" HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" -DIO_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-dio-client" - DART_IO_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-dart-io-http-client" @@ -61,10 +59,9 @@ $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" native_flutter_proxy $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_http_client:{'path':'$HTTP_WRAPPER_PACKAGE_PATH'}" -$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_dio_client:{'path':'$DIO_WRAPPER_PACKAGE_PATH'}" - $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_flutter_dart_io_http_client:{'path':'$DART_IO_WRAPPER_PACKAGE_PATH'}" +$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" dio echo "update min sdk version in android gradle file" diff --git a/packages/bugsnag-flutter-dart-io-http-client b/packages/bugsnag-flutter-dart-io-http-client index a3ea6f1..cd67b71 160000 --- a/packages/bugsnag-flutter-dart-io-http-client +++ b/packages/bugsnag-flutter-dart-io-http-client @@ -1 +1 @@ -Subproject commit a3ea6f1f0bf32080f57fe54391469153456ebe16 +Subproject commit cd67b716a8aa51f18fcea785e7a68ce24060895b diff --git a/packages/bugsnag-flutter-dio-client b/packages/bugsnag-flutter-dio-client deleted file mode 160000 index e03066c..0000000 --- a/packages/bugsnag-flutter-dio-client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e03066c7e01541a563f5d41cda8a140aa4bd484b From c3cae041438b0e865ffeade6a9df9e71ee977dcc Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Tue, 26 Mar 2024 15:19:09 +0100 Subject: [PATCH 44/66] PLAT-11822 make Flutter Performance client a singleton (#44) * convert to singleton * Update packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart Co-authored-by: Tom Longridge * review changes * typo --------- Co-authored-by: Tom Longridge --- .../bugsnag_performance_example/lib/main.dart | 20 ++--- .../auto_instrument_app_starts_scenario.dart | 8 +- ..._instrument_navigation_basic_scenario.dart | 8 +- .../lib/scenarios/dart_io_get_scenario.dart | 2 +- .../scenarios/dio_callback_cancel_span.dart | 9 ++- .../scenarios/dio_callback_edit_scenario.dart | 9 ++- .../lib/scenarios/dio_get_scenario.dart | 6 +- .../lib/scenarios/dio_post_scenario.dart | 5 +- .../scenarios/http_callback_cancel_span.dart | 8 +- .../http_callback_edit_scenario.dart | 6 +- ...ttp_get_multiple_subscribers_scenario.dart | 4 +- .../lib/scenarios/http_get_scenario.dart | 2 +- .../lib/scenarios/http_post_scenario.dart | 2 +- .../lib/scenarios/initial_p_scenario.dart | 10 +-- .../lib/scenarios/make_current_context.dart | 6 +- ...nual_span_isfirstclass_false_scenario.dart | 10 ++- .../new_zone_new_context_scenario.dart | 8 +- .../lib/scenarios/pass_context_scenario.dart | 7 +- .../probability_expiry_scenario.dart | 8 +- .../lib/scenarios/scenario.dart | 10 +-- .../simple_nested_span_scenario.dart | 4 +- .../lib/bugsnag_flutter_performance.dart | 76 ++++++++++++++++++- .../src/bugsnag_performance_public_api.dart | 68 ----------------- 23 files changed, 154 insertions(+), 142 deletions(-) delete mode 100644 packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index b3c558b..f2f7129 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { - BugsnagPerformance.start(apiKey: apiKey); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); - BugsnagPerformance.measureRunApp(() async => runApp(const MainApp())); + bugsnag_performance.start(apiKey: apiKey); + http.addSubscriber(bugsnag_performance.networkInstrumentation); + bugsnag_performance.measureRunApp(() async => runApp(const MainApp())); } class MainApp extends StatelessWidget { @@ -18,8 +18,10 @@ class MainApp extends StatelessWidget { return MaterialApp( home: Scaffold( body: Center( - child: Row( // Use Column for vertical alignment - mainAxisAlignment: MainAxisAlignment.center, // Center the buttons horizontally + child: Row( + // Use Column for vertical alignment + mainAxisAlignment: + MainAxisAlignment.center, // Center the buttons horizontally children: [ TextButton( onPressed: sendCustomSpan, // Replace with your actual function @@ -27,7 +29,8 @@ class MainApp extends StatelessWidget { ), SizedBox(width: 20), // Spacing between buttons, adjust as needed TextButton( - onPressed: sendNetworkSpan, // You'll need to define this function + onPressed: + sendNetworkSpan, // You'll need to define this function child: Text('Send Network Span'), ), ], @@ -38,13 +41,10 @@ class MainApp extends StatelessWidget { } void sendCustomSpan() { - BugsnagPerformance.startSpan('test').end(); + bugsnag_performance.startSpan('test').end(); } void sendNetworkSpan() { http.get(Uri.parse('https://httpbin.org/get')); } } - - - diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart index f3b9880..f19836a 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart @@ -6,12 +6,12 @@ import 'scenario.dart'; class AutoInstrumentAppStartsScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", true); - BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 1000); - BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", true); + bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); + bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); - BugsnagPerformance.measureRunApp(() async => await Duration(seconds: 1)); + bugsnag_performance.measureRunApp(() async => await Duration(seconds: 1)); setMaxBatchSize(4); } } diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart index 83d8375..2d743a5 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart @@ -8,10 +8,10 @@ import 'scenario.dart'; class AutoInstrumentNavigationBasicScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - BugsnagPerformance.setExtraConfig("instrumentNavigation", true); - BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 1000); - BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + bugsnag_performance.setExtraConfig("instrumentNavigation", true); + bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); + bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); setMaxBatchSize(1); diff --git a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart index 3b0a59a..4845a97 100644 --- a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart @@ -11,7 +11,7 @@ class DartIoGetScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - dartIo.addSubscriber(BugsnagPerformance.networkInstrumentation); + dartIo.addSubscriber(bugsnag_performance.networkInstrumentation); final client = dartIo.BugsnagHttpClient(); HttpClientRequest request = await client.getUrl(FixtureConfig.MAZE_HOST); await request.close(); diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart index abb8607..9ffbfe6 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -3,21 +3,22 @@ import 'package:bugsnag_http_client/bugsnag_http_client.dart'; import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; -import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' + as dart_io; import 'package:dio/dio.dart'; class DIOCallbackCancelSpanScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - await BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), networkRequestCallback: (info) { return null; }); setMaxBatchSize(1); - dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + dart_io.addSubscriber(bugsnag_performance.networkInstrumentation); final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { diff --git a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart index 8a77d5c..9d458d1 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart @@ -3,14 +3,15 @@ import 'package:bugsnag_http_client/bugsnag_http_client.dart'; import 'package:dio/io.dart'; import '../main.dart'; import 'scenario.dart'; -import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' + as dart_io; import 'package:dio/dio.dart'; class DIOCallbackEditScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - await BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), networkRequestCallback: (info) { @@ -19,7 +20,7 @@ class DIOCallbackEditScenario extends Scenario { }); setMaxBatchSize(1); - dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + dart_io.addSubscriber(bugsnag_performance.networkInstrumentation); final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { diff --git a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart index 0ed3d62..6196e81 100644 --- a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart @@ -1,4 +1,5 @@ -import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' + as dart_io; import 'package:dio/dio.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:dio/io.dart'; @@ -11,7 +12,7 @@ class DIOGetScenario extends Scenario { await startBugsnag(); setMaxBatchSize(1); - dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + dart_io.addSubscriber(bugsnag_performance.networkInstrumentation); final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { @@ -20,6 +21,5 @@ class DIOGetScenario extends Scenario { ); dio.get(FixtureConfig.MAZE_HOST.toString()); - } } diff --git a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart index e5f2c24..bd0e356 100644 --- a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart @@ -1,4 +1,5 @@ -import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io; +import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' + as dart_io; import 'package:dio/dio.dart'; import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:dio/io.dart'; @@ -11,7 +12,7 @@ class DIOPostScenario extends Scenario { await startBugsnag(); setMaxBatchSize(1); - dart_io.addSubscriber(BugsnagPerformance.networkInstrumentation); + dart_io.addSubscriber(bugsnag_performance.networkInstrumentation); final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { diff --git a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart index a155588..a05f98f 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart @@ -6,17 +6,17 @@ import 'scenario.dart'; class HttpCallbackCancelSpanScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - await BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), networkRequestCallback: (info) { return null; }); setMaxBatchSize(1); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); await Future.delayed(const Duration(seconds: 10)); - BugsnagPerformance.startSpan('HttpCallbackCancelSpanScenario').end(); + bugsnag_performance.startSpan('HttpCallbackCancelSpanScenario').end(); } } diff --git a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart index 703bb7d..5f53620 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart @@ -6,8 +6,8 @@ import 'scenario.dart'; class HttpCallbackEditScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - await BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), networkRequestCallback: (info) { @@ -15,7 +15,7 @@ class HttpCallbackEditScenario extends Scenario { return info; }); setMaxBatchSize(1); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart index fd0f58b..20322fc 100644 --- a/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_get_multiple_subscribers_scenario.dart @@ -8,8 +8,8 @@ class HttpGetMultipleSubscribersScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); http.get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_get_scenario.dart b/features/fixture_resources/lib/scenarios/http_get_scenario.dart index f63805c..3e10c15 100644 --- a/features/fixture_resources/lib/scenarios/http_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_get_scenario.dart @@ -8,7 +8,7 @@ class HttpGetScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); http.get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_post_scenario.dart b/features/fixture_resources/lib/scenarios/http_post_scenario.dart index 633e5d3..21970f9 100644 --- a/features/fixture_resources/lib/scenarios/http_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_post_scenario.dart @@ -8,7 +8,7 @@ class HttpPostScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - http.addSubscriber(BugsnagPerformance.networkInstrumentation); + http.addSubscriber(bugsnag_performance.networkInstrumentation); http.BugSnagHttpClient() .post(FixtureConfig.MAZE_HOST, body: {"key": "value"}); } diff --git a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart index 349604c..6ae46e7 100644 --- a/features/fixture_resources/lib/scenarios/initial_p_scenario.dart +++ b/features/fixture_resources/lib/scenarios/initial_p_scenario.dart @@ -9,11 +9,11 @@ import 'package:http/http.dart' as http; class InitialPScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - BugsnagPerformance.setExtraConfig("instrumentNavigation", false); - BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 1000); - BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 25000); - BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + bugsnag_performance.setExtraConfig("instrumentNavigation", false); + bugsnag_performance.setExtraConfig("probabilityRequestsPause", 1000); + bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 25000); + bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); setMaxBatchSize(1); diff --git a/features/fixture_resources/lib/scenarios/make_current_context.dart b/features/fixture_resources/lib/scenarios/make_current_context.dart index c199aa5..39bc013 100644 --- a/features/fixture_resources/lib/scenarios/make_current_context.dart +++ b/features/fixture_resources/lib/scenarios/make_current_context.dart @@ -8,10 +8,10 @@ class MakeCurrentContextScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(3); - final span1 = BugsnagPerformance.startSpan('span1'); + final span1 = bugsnag_performance.startSpan('span1'); final span2 = - BugsnagPerformance.startSpan('span2', makeCurrentContext: false); - final span3 = BugsnagPerformance.startSpan('span3'); + bugsnag_performance.startSpan('span2', makeCurrentContext: false); + final span3 = bugsnag_performance.startSpan('span3'); span3.end(); span2.end(); span1.end(); diff --git a/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart b/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart index 6004797..4e3804e 100644 --- a/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_span_isfirstclass_false_scenario.dart @@ -6,9 +6,11 @@ class ManualSpanIsFirstClassFalseScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(1); - BugsnagPerformance.startSpan( - 'ManualSpanIsFirstClassFalseScenario', - isFirstClass: false, - ).end(); + bugsnag_performance + .startSpan( + 'ManualSpanIsFirstClassFalseScenario', + isFirstClass: false, + ) + .end(); } } diff --git a/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart index dddd527..0a38351 100644 --- a/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart +++ b/features/fixture_resources/lib/scenarios/new_zone_new_context_scenario.dart @@ -8,16 +8,16 @@ class NewZoneNewContextScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(4); - final span1 = BugsnagPerformance.startSpan('span1'); + final span1 = bugsnag_performance.startSpan('span1'); runZoned(() { - final span3 = BugsnagPerformance.startSpan('span3'); - final span4 = BugsnagPerformance.startSpan('span4'); + final span3 = bugsnag_performance.startSpan('span3'); + final span4 = bugsnag_performance.startSpan('span4'); span4.end(); span3.end(); }, zoneValues: {}); - final span2 = BugsnagPerformance.startSpan('span2'); + final span2 = bugsnag_performance.startSpan('span2'); span2.end(); span1.end(); } diff --git a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart index 2121524..ba2c0fb 100644 --- a/features/fixture_resources/lib/scenarios/pass_context_scenario.dart +++ b/features/fixture_resources/lib/scenarios/pass_context_scenario.dart @@ -8,10 +8,11 @@ class PassContextToNewZoneScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(3); - final span1 = BugsnagPerformance.startSpan('span1'); + final span1 = bugsnag_performance.startSpan('span1'); runZoned(() { - final span2 = BugsnagPerformance.startSpan('span2', parentContext: span1); - final span3 = BugsnagPerformance.startSpan('span3'); + final span2 = + bugsnag_performance.startSpan('span2', parentContext: span1); + final span3 = bugsnag_performance.startSpan('span3'); span2.end(); span3.end(); }, zoneValues: {}); diff --git a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart index 5e35bfa..f38996d 100644 --- a/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart +++ b/features/fixture_resources/lib/scenarios/probability_expiry_scenario.dart @@ -9,10 +9,10 @@ import 'package:http/http.dart' as http; class ProbabilityExpiryScenario extends Scenario { @override Future run() async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - BugsnagPerformance.setExtraConfig("probabilityRequestsPause", 100); - BugsnagPerformance.setExtraConfig("probabilityValueExpireTime", 100); - BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + bugsnag_performance.setExtraConfig("probabilityRequestsPause", 100); + bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 100); + bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); setMaxBatchSize(1); diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index c8c5f46..596cfdd 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -19,15 +19,15 @@ abstract class Scenario { Future run(); void doSimpleSpan(String name) { - BugsnagPerformance.startSpan(name).end(); + bugsnag_performance.startSpan(name).end(); } void setMaxBatchSize(int size) { - BugsnagPerformance.setExtraConfig("maxBatchSize", size); + bugsnag_performance.setExtraConfig("maxBatchSize", size); } void setMaxBatchAge(int milliseconds) { - BugsnagPerformance.setExtraConfig("maxBatchAge", milliseconds); + bugsnag_performance.setExtraConfig("maxBatchAge", milliseconds); } Future startBugsnag({ @@ -35,8 +35,8 @@ abstract class Scenario { List? enabledReleaseStages, String? appVersion, }) async { - BugsnagPerformance.setExtraConfig("instrumentAppStart", false); - await BugsnagPerformance.start( + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), releaseStage: releaseStage, diff --git a/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart index c0aa08f..88c5e92 100644 --- a/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/simple_nested_span_scenario.dart @@ -6,8 +6,8 @@ class SimpleNestedSpanScenario extends Scenario { Future run() async { await startBugsnag(); setMaxBatchSize(2); - final span1 = BugsnagPerformance.startSpan('span1'); - final span2 = BugsnagPerformance.startSpan('span2'); + final span1 = bugsnag_performance.startSpan('span1'); + final span2 = bugsnag_performance.startSpan('span2'); span2.end(); span1.end(); } diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index f67cf70..50a0029 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -1,4 +1,78 @@ library bugsnag_flutter_performance; +import 'dart:async'; + +import 'package:bugsnag_flutter_performance/src/bugsnag_network_request_info.dart'; +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; +import 'package:bugsnag_flutter_performance/src/span_context.dart'; + +import 'bugsnag_flutter_performance.dart'; +export 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart' + show bugsnag_performance; export 'src/span.dart' show BugsnagPerformanceSpan; -export 'src/bugsnag_performance_public_api.dart' show BugsnagPerformance; + +class BugsnagPerformance { + BugsnagPerformance._internal(); + + static final BugsnagPerformanceClientImpl _client = + BugsnagPerformanceClientImpl(); + + Future start({ + required String apiKey, + Uri? endpoint, + BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? + networkRequestCallback, + String? releaseStage, + List? enabledReleaseStages, + String? appVersion, + }) { + return _client.start( + apiKey: apiKey, + endpoint: endpoint, + networkRequestCallback: networkRequestCallback, + releaseStage: releaseStage, + enabledReleaseStages: enabledReleaseStages, + appVersion: appVersion, + ); + } + + BugsnagPerformanceSpan startSpan(String name, + {DateTime? startTime, + BugsnagPerformanceSpanContext? parentContext, + bool? makeCurrentContext = true, + bool? isFirstClass = true}) { + return _client.startSpan( + name, + startTime: startTime, + parentContext: parentContext, + makeCurrentContext: makeCurrentContext, + attributes: BugsnagPerformanceSpanAttributes( + category: 'custom', + isFirstClass: isFirstClass, + ), + ); + } + + BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod) { + return _client.startNetworkSpan(url, httpMethod.toUpperCase()); + } + + Future measureRunApp( + FutureOr Function() runApp, + ) async { + await _client.measureRunApp(runApp); + } + + // Intended for internal-use only + void setExtraConfig(String key, dynamic value) { + _client.setExtraConfig(key, value); + } + + dynamic networkInstrumentation(dynamic data) { + return _client.networkInstrumentation(data); + } +} + +// ignore: non_constant_identifier_names +final BugsnagPerformance bugsnag_performance = BugsnagPerformance._internal(); diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart deleted file mode 100644 index 1e0f35b..0000000 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_performance_public_api.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:async'; - -import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; -import 'package:bugsnag_flutter_performance/src/span_context.dart'; - -import '../bugsnag_flutter_performance.dart'; -import 'bugsnag_network_request_info.dart'; -import 'client.dart'; - -class BugsnagPerformance { - static final BugsnagPerformanceClientImpl _client = - BugsnagPerformanceClientImpl(); - - static Future start({ - required String apiKey, - Uri? endpoint, - BugsnagNetworkRequestInfo? Function(BugsnagNetworkRequestInfo)? - networkRequestCallback, - String? releaseStage, - List? enabledReleaseStages, - String? appVersion, - }) async { - return _client.start( - apiKey: apiKey, - endpoint: endpoint, - networkRequestCallback: networkRequestCallback, - releaseStage: releaseStage, - enabledReleaseStages: enabledReleaseStages, - appVersion: appVersion, - ); - } - - static BugsnagPerformanceSpan startSpan(String name, - {DateTime? startTime, - BugsnagPerformanceSpanContext? parentContext, - bool? makeCurrentContext = true, - bool? isFirstClass = true}) { - return _client.startSpan( - name, - startTime: startTime, - parentContext: parentContext, - makeCurrentContext: makeCurrentContext, - attributes: BugsnagPerformanceSpanAttributes( - category: "custom", - isFirstClass: isFirstClass, - ), - ); - } - - static BugsnagPerformanceSpan startNetworkSpan( - String url, String httpMethod) { - return _client.startNetworkSpan(url, httpMethod.toUpperCase()); - } - - static Future measureRunApp( - FutureOr Function() runApp, - ) async { - await _client.measureRunApp(runApp); - } - - static void setExtraConfig(String key, dynamic value) { - _client.setExtraConfig(key, value); - } - - static dynamic networkInstrumentation(dynamic data) { - return _client.networkInstrumentation(data); - } -} From 25f06d8017ce9d3629e1d5bb46dc323414820369 Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Wed, 27 Mar 2024 09:23:59 +0000 Subject: [PATCH 45/66] docs: pre-release content --- CHANGELOG.md | 4 +-- LICENSE | 4 +-- README.md | 34 +++++++++++++++++++ .../bugsnag_flutter_performance/CHANGELOG.md | 3 -- packages/bugsnag_flutter_performance/LICENSE | 1 - .../bugsnag_flutter_performance/README.md | 15 -------- 6 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 README.md delete mode 100644 packages/bugsnag_flutter_performance/CHANGELOG.md delete mode 100644 packages/bugsnag_flutter_performance/LICENSE delete mode 100644 packages/bugsnag_flutter_performance/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3a2cd..edadeb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ Changelog ========= -## TBD +## 1.0.0 (TBD) -Initial preview release +Initial release diff --git a/LICENSE b/LICENSE index e578c23..7129278 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Bugsnag +Copyright (c) 2024 Bugsnag Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f53984 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ + + +[![Documentation](https://img.shields.io/badge/documentation-latest-blue.svg)](https://docs.bugsnag.com/performance/flutter/) +[![Build status](https://badge.buildkite.com/700ab6c9d7b51c72fa52459ad023b503692bde8bf5337b93a0.svg?branch=main)](https://buildkite.com/bugsnag/bugsnag-flutter-performance) + +Monitor the start-up, navigation and network requests of your game and see the results in your [BugSnag](https://www.bugsnag.com) dashboard. + +## Features + +- Reporting of app start-up time +- Supports instrumentation of navigation events to report screen load times +- Wrappers to report network requests made using the [`http`](https://pub.dev/packages/http), [`dart:io`](https://api.flutter.dev/flutter/dart-io/dart-io-library.html) and [`dio`](https://pub.dev/packages/dio) libraries + +## Getting started + +For integration instructions, see our online docs: [docs.bugsnag.com/performance/flutter](https://docs.bugsnag.com/performance/flutter/) + +## Support + +* [Read the integration guide](https://docs.bugsnag.com/performance/flutter/) +* [Search open and closed issues](https://github.com/bugsnag/bugsnag-flutter-performance/issues?utf8=✓&q=is%3Aissue) for similar problems +* [Report a bug or request a feature](https://github.com/bugsnag/bugsnag-flutter-performance/issues/new) + +## License + +The BugSnag Flutter Performance SDK is free software released under the MIT License. See the [LICENSE](./LICENSE) for details. \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/CHANGELOG.md b/packages/bugsnag_flutter_performance/CHANGELOG.md deleted file mode 100644 index 41cc7d8..0000000 --- a/packages/bugsnag_flutter_performance/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.0.1 - -* TODO: Describe initial release. diff --git a/packages/bugsnag_flutter_performance/LICENSE b/packages/bugsnag_flutter_performance/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/packages/bugsnag_flutter_performance/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/packages/bugsnag_flutter_performance/README.md b/packages/bugsnag_flutter_performance/README.md deleted file mode 100644 index 6498765..0000000 --- a/packages/bugsnag_flutter_performance/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# bugsnag_flutter_performance - -A new Flutter plugin project. - -## Getting Started - -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter development, view the -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. - From 03e186a0615b586013f9dbcbc284956a324f87dd Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:29:34 +0100 Subject: [PATCH 46/66] Navigation instrumentation (#43) * Navigation instrumentation - deferring span closing and phased building * Fixture and lint changes * Added complex E2E tests * Removed view load instrumentation and implemented requested changes to navigation instrumentation * Removed unnecessary prints * Reverted submodule change * Fixed E2E test flakes * Changes requested in code review * Requested change to navigation span names * Updated fixture code --------- Co-authored-by: Robert --- features/automatic_spans.feature | 119 ++++++++++- features/fixture_resources/lib/main.dart | 1 + ...ument_navigation_basic_defer_scenario.dart | 79 +++++++ ..._instrument_navigation_basic_scenario.dart | 10 +- ...ent_navigation_complex_defer_scenario.dart | 110 ++++++++++ ...navigation_nested_navigation_scenario.dart | 136 ++++++++++++ ...ment_navigation_push_and_pop_scenario.dart | 71 +++++++ .../lib/scenarios/scenario.dart | 6 + .../lib/scenarios/scenarios.dart | 12 ++ features/steps/flutter_steps.rb | 8 + packages/bugsnag-flutter-navigator-observer | 2 +- .../lib/bugsnag_flutter_performance.dart | 4 + .../lib/src/client.dart | 9 +- .../app_start_instrumentation.dart | 0 .../navigation_instrumentation.dart | 194 ++++++++++++++++++ .../navigation_instrumentation_node.dart | 88 ++++++++ .../widget_instrumentation_state.dart | 24 +++ .../navigation_instrumentation.dart | 42 ---- .../widgets/bugsnag_loading_indicator.dart | 36 ++++ .../widgets/bugsnag_navigation_container.dart | 48 +++++ .../widget_instrumentation_node_provider.dart | 18 ++ 21 files changed, 963 insertions(+), 54 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_defer_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_navigation_complex_defer_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/auto_instrument_navigation_push_and_pop_scenario.dart rename packages/bugsnag_flutter_performance/lib/src/instrumentation/{ => app_start}/app_start_instrumentation.dart (100%) create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation_node.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/widget_instrumentation_state.dart delete mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_loading_indicator.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_navigation_container.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/widgets/widget_instrumentation_node_provider.dart diff --git a/features/automatic_spans.feature b/features/automatic_spans.feature index c89a130..8fe1875 100644 --- a/features/automatic_spans.feature +++ b/features/automatic_spans.feature @@ -31,9 +31,126 @@ Feature: Automatic instrumentation spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" - * a span field "name" equals "[Navigation]/AutoInstrumentNavigationBasicScenarioScreen" + * a span field "name" equals "[Navigation]basic_navigation_scenario" + * a span string attribute "bugsnag.span.category" equals "navigation" + * a span string attribute "bugsnag.navigation.route" equals "basic_navigation_scenario" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "frame_render" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span bool attribute "bugsnag.span.first_class" does not exist + + Scenario: AutoInstrumentNavigationBasicDeferScenario + Given I run "AutoInstrumentNavigationBasicDeferScenario" + And I wait for 5 seconds + * no span named "[Navigation]basic_defer_navigation_scenario" exists + And I invoke "step2" + Then I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[Navigation]basic_defer_navigation_scenario" + * a span string attribute "bugsnag.navigation.route" equals "basic_defer_navigation_scenario" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "loading_indicator" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist + + Scenario: AutoInstrumentNavigationComplexDeferScenario + Given I run "AutoInstrumentNavigationComplexDeferScenario" + And I wait for 3 seconds + * no span named "[Navigation]complex_defer_navigation_scenario" exists + Then I invoke "step2" + And I wait for 3 seconds + * no span named "[Navigation]complex_defer_navigation_scenario" exists + Then I invoke "step3" + And I wait for 3 seconds + * no span named "[Navigation]complex_defer_navigation_scenario" exists + Then I invoke "step4" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[Navigation]complex_defer_navigation_scenario" + * a span string attribute "bugsnag.navigation.route" equals "complex_defer_navigation_scenario" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "loading_indicator" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist + + Scenario: AutoInstrumentNavigationNestedNavigationScenario + Given I run "AutoInstrumentNavigationNestedNavigationScenario" + And I wait for 3 seconds + * no span named "[Navigation]nested_defer_navigation_scenario_parent" exists + Then I invoke "step2" + And I wait for 2 spans + * a span field "name" equals "[Navigation]nested_defer_navigation_scenario_parent" + * a span string attribute "bugsnag.navigation.route" equals "nested_defer_navigation_scenario_parent" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "loading_indicator" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" + + * a span field "name" equals "[Navigation]nested_scenario_child_navigator/nested_scenario_child_route_initial" + * a span string attribute "bugsnag.navigation.route" equals "nested_scenario_child_route_initial" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "loading_indicator" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" + * a span string attribute "bugsnag.navigation.navigator" equals "nested_scenario_child_navigator" + Then I invoke "step3" + And I wait for 3 spans + * a span field "name" equals "[Navigation]nested_scenario_child_navigator/nested_scenario_child_route_2" + * a span string attribute "bugsnag.navigation.route" equals "nested_scenario_child_route_2" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "frame_render" + * a span string attribute "bugsnag.navigation.previous_route" equals "nested_scenario_child_route_initial" + * a span string attribute "bugsnag.navigation.navigator" equals "nested_scenario_child_navigator" + Then I invoke "step4" + And I wait for 4 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[Navigation]nested_scenario_child_navigator/nested_scenario_child_route_3" + * a span string attribute "bugsnag.navigation.route" equals "nested_scenario_child_route_3" + * a span string attribute "bugsnag.navigation.triggered_by" equals "replace" + * a span string attribute "bugsnag.navigation.ended_by" equals "frame_render" + * a span string attribute "bugsnag.navigation.previous_route" equals "nested_scenario_child_route_2" + * a span string attribute "bugsnag.navigation.navigator" equals "nested_scenario_child_navigator" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist + + Scenario: AutoInstrumentNavigationPushAndPopScenario + Given I run "AutoInstrumentNavigationPushAndPopScenario" + And I wait for 1 span + * a span field "name" equals "[Navigation]push_and_pop_scenario" + * a span string attribute "bugsnag.navigation.route" equals "push_and_pop_scenario" + * a span string attribute "bugsnag.navigation.triggered_by" equals "push" + * a span string attribute "bugsnag.navigation.ended_by" equals "frame_render" + * a span string attribute "bugsnag.navigation.previous_route" equals "/" + And I invoke "step2" + Then I wait for 2 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "[Navigation]push_and_pop_scenario" + * a span string attribute "bugsnag.navigation.route" equals "push_and_pop_scenario" + * a span string attribute "bugsnag.navigation.triggered_by" equals "pop" + * a span string attribute "bugsnag.navigation.ended_by" equals "frame_render" + * a span string attribute "bugsnag.navigation.previous_route" equals "push_and_pop_scenario" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" does not exist \ No newline at end of file diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index 3a35d7a..dc120f2 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -273,6 +273,7 @@ class _HomePageState extends State { log('Running scenario'); _currentScenario = scenario; + scenario.runCommandCallback = () => _onRunCommand(context, retry: true); await scenario.run(); Widget? scenarioWidget = scenario.createWidget(); if (scenarioWidget != null) { diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_defer_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_defer_scenario.dart new file mode 100644 index 0000000..58c61b9 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_defer_scenario.dart @@ -0,0 +1,79 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/material.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentNavigationBasicDeferScenario extends Scenario { + final _key = + GlobalKey<_AutoInstrumentNavigationBasicDeferScenarioScreenState>(); + + @override + Future run() async { + setInstrumentsNavigation(true); + await startBugsnag(); + setMaxBatchSize(1); + } + + @override + Widget? createWidget() { + return AutoInstrumentNavigationBasicDeferScenarioScreen( + key: _key, + runCommandCallback: () => runCommandCallback!(), + ); + } + + @override + RouteSettings? routeSettings() { + return const RouteSettings(name: 'basic_defer_navigation_scenario'); + } + + void step2() { + _key.currentState!.setStage(2); + } + + @override + void invokeMethod(String name) { + switch (name) { + case 'step2': + step2(); + break; + default: + break; + } + } +} + +class AutoInstrumentNavigationBasicDeferScenarioScreen extends StatefulWidget { + const AutoInstrumentNavigationBasicDeferScenarioScreen({ + super.key, + required this.runCommandCallback, + }); + final void Function() runCommandCallback; + + @override + State createState() => + _AutoInstrumentNavigationBasicDeferScenarioScreenState(); +} + +class _AutoInstrumentNavigationBasicDeferScenarioScreenState + extends State { + var _stage = 1; + + @override + Widget build(BuildContext context) { + if (_stage < 2) { + return GestureDetector( + child: const BugsnagLoadingIndicator(child: Text('Loading...')), + onTap: () => widget.runCommandCallback(), + ); + } + return Text('AutoInstrumentNavigationBasicDeferScenarioScreen'); + } + + void setStage(int stage) { + setState(() { + _stage = stage; + }); + } +} diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart index 2d743a5..4c9f2f6 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_basic_scenario.dart @@ -8,12 +8,8 @@ import 'scenario.dart'; class AutoInstrumentNavigationBasicScenario extends Scenario { @override Future run() async { - bugsnag_performance.setExtraConfig("instrumentAppStart", false); - bugsnag_performance.setExtraConfig("instrumentNavigation", true); - bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); - bugsnag_performance.start( - apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + setInstrumentsNavigation(true); + await startBugsnag(); setMaxBatchSize(1); } @@ -24,6 +20,6 @@ class AutoInstrumentNavigationBasicScenario extends Scenario { @override RouteSettings? routeSettings() { - return RouteSettings(name: 'AutoInstrumentNavigationBasicScenarioScreen'); + return RouteSettings(name: 'basic_navigation_scenario'); } } diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_complex_defer_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_complex_defer_scenario.dart new file mode 100644 index 0000000..53536ca --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_complex_defer_scenario.dart @@ -0,0 +1,110 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/material.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentNavigationComplexDeferScenario extends Scenario { + final _key = + GlobalKey<_AutoInstrumentNavigationComplexDeferScenarioScreenState>(); + + @override + Future run() async { + setInstrumentsNavigation(true); + await startBugsnag(); + setMaxBatchSize(1); + } + + @override + Widget? createWidget() { + return AutoInstrumentNavigationComplexDeferScenarioScreen( + key: _key, + runCommandCallback: () => runCommandCallback!(), + ); + } + + @override + RouteSettings? routeSettings() { + return const RouteSettings(name: 'complex_defer_navigation_scenario'); + } + + void step2() { + _key.currentState!.setStage(2); + } + + void step3() { + _key.currentState!.setStage(3); + } + + void step4() { + _key.currentState!.setStage(4); + } + + @override + void invokeMethod(String name) { + switch (name) { + case 'step2': + step2(); + break; + case 'step3': + step3(); + break; + case 'step4': + step4(); + break; + default: + break; + } + } +} + +class AutoInstrumentNavigationComplexDeferScenarioScreen + extends StatefulWidget { + const AutoInstrumentNavigationComplexDeferScenarioScreen({ + super.key, + required this.runCommandCallback, + }); + final void Function() runCommandCallback; + + @override + State createState() => + _AutoInstrumentNavigationComplexDeferScenarioScreenState(); +} + +class _AutoInstrumentNavigationComplexDeferScenarioScreenState + extends State { + var _stage = 1; + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: Container( + color: Colors.white, + child: Row( + children: [ + if (_stage < 4) + BugsnagLoadingIndicator( + child: Column(children: [ + if (_stage < 3) + const BugsnagLoadingIndicator( + child: Text('Still loading...'), + ), + if (_stage < 2) + const BugsnagLoadingIndicator( + child: CircularProgressIndicator(), + ) + ]), + ), + const Text('AutoInstrumentNavigationComplexDeferScenarioScreen') + ], + ), + ), + onTap: () => widget.runCommandCallback()); + } + + void setStage(int stage) { + setState(() { + _stage = stage; + }); + } +} diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart new file mode 100644 index 0000000..a91cd5e --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart @@ -0,0 +1,136 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_navigator_observer/bugsnag_flutter_navigator_observer.dart'; +import 'package:flutter/material.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentNavigationNestedNavigationScenario extends Scenario { + final _key = GlobalKey< + _AutoInstrumentNavigationNestedNavigationScenarioSubScreenState>(); + Route? routeToReplace; + + @override + Future run() async { + setInstrumentsNavigation(true); + await startBugsnag(); + setMaxBatchSize(1); + } + + @override + Widget? createWidget() { + return BugsnagNavigationContainer( + child: Navigator( + observers: [ + BugsnagNavigatorObserver( + navigatorName: "nested_scenario_child_navigator") + ], + pages: [ + MaterialPage( + child: AutoInstrumentNavigationNestedNavigationScenarioSubScreen( + key: _key, runCommandCallback: () => runCommandCallback!()), + name: 'nested_scenario_child_route_initial', + ) + ], + ), + ); + } + + @override + RouteSettings? routeSettings() { + return const RouteSettings( + name: 'nested_defer_navigation_scenario_parent', + ); + } + + void step2() { + _key.currentState!.finishLoading(); + } + + void step3() { + routeToReplace = MaterialPageRoute( + builder: (context) => GestureDetector( + child: Container( + color: Colors.white, + ), + onTap: () => runCommandCallback!(), + ), + settings: const RouteSettings( + name: 'nested_scenario_child_route_2', + ), + ); + Navigator.of(_key.currentContext!).push(routeToReplace!); + } + + void step4() { + final route = MaterialPageRoute( + builder: (context) => Container(), + settings: const RouteSettings( + name: 'nested_scenario_child_route_3', + ), + ); + Navigator.of(_key.currentContext!).replace( + oldRoute: routeToReplace!, + newRoute: route, + ); + } + + @override + void invokeMethod(String name) { + switch (name) { + case 'step2': + step2(); + break; + case 'step3': + step3(); + break; + case 'step4': + step4(); + break; + default: + break; + } + } +} + +class AutoInstrumentNavigationNestedNavigationScenarioSubScreen + extends StatefulWidget { + const AutoInstrumentNavigationNestedNavigationScenarioSubScreen({ + super.key, + required this.runCommandCallback, + }); + final void Function() runCommandCallback; + + @override + State + createState() => + _AutoInstrumentNavigationNestedNavigationScenarioSubScreenState(); +} + +class _AutoInstrumentNavigationNestedNavigationScenarioSubScreenState + extends State { + var isLoading = true; + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: Container( + color: Colors.white, + child: Row( + children: [ + if (isLoading) + const BugsnagLoadingIndicator(child: Text('Loading...')), + const Text('Screen'), + ], + ), + ), + onTap: () => widget.runCommandCallback(), + ); + } + + finishLoading() { + setState(() { + isLoading = false; + }); + } +} diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_push_and_pop_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_push_and_pop_scenario.dart new file mode 100644 index 0000000..d52f5d7 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_push_and_pop_scenario.dart @@ -0,0 +1,71 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:flutter/material.dart'; +import 'package:mazerunner/main.dart'; + +import 'scenario.dart'; + +class AutoInstrumentNavigationPushAndPopScenario extends Scenario { + final _key = + GlobalKey(); + + @override + Future run() async { + setInstrumentsNavigation(true); + await startBugsnag(); + setMaxBatchSize(1); + } + + @override + Widget? createWidget() { + return AutoInstrumentNavigationPushAndPopScenarioScreen( + key: _key, + runCommandCallback: () => runCommandCallback!(), + ); + } + + @override + RouteSettings? routeSettings() { + return const RouteSettings(name: 'push_and_pop_scenario'); + } + + void step2() { + Navigator.of(_key.currentContext!).pop(); + } + + @override + void invokeMethod(String name) { + switch (name) { + case 'step2': + step2(); + break; + default: + break; + } + } +} + +class AutoInstrumentNavigationPushAndPopScenarioScreen extends StatefulWidget { + const AutoInstrumentNavigationPushAndPopScenarioScreen({ + super.key, + required this.runCommandCallback, + }); + final void Function() runCommandCallback; + + @override + State createState() => + AutoInstrumentNavigationPushAndPopScenarioScreenState(); +} + +class AutoInstrumentNavigationPushAndPopScenarioScreenState + extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + child: Container( + child: Text('AutoInstrumentNavigationPushAndPopScenarioScreen'), + color: Colors.white, + ), + onTap: () => widget.runCommandCallback(), + ); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenario.dart b/features/fixture_resources/lib/scenarios/scenario.dart index 596cfdd..012f522 100644 --- a/features/fixture_resources/lib/scenarios/scenario.dart +++ b/features/fixture_resources/lib/scenarios/scenario.dart @@ -7,6 +7,7 @@ import '../main.dart'; abstract class Scenario { String? extraConfig; + void Function()? runCommandCallback; Future clearPersistentData() async { print('[MazeRunner] Clearing Persistent Data...'); @@ -30,12 +31,17 @@ abstract class Scenario { bugsnag_performance.setExtraConfig("maxBatchAge", milliseconds); } + void setInstrumentsNavigation(bool value) { + bugsnag_performance.setExtraConfig("instrumentNavigation", value); + } + Future startBugsnag({ String? releaseStage, List? enabledReleaseStages, String? appVersion, }) async { bugsnag_performance.setExtraConfig("instrumentAppStart", false); + bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); await bugsnag_performance.start( apiKey: '12312312312312312312312312312312', endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index 452ece2..bbff8de 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -1,4 +1,8 @@ +import 'package:mazerunner/scenarios/auto_instrument_navigation_basic_defer_scenario.dart'; import 'package:mazerunner/scenarios/auto_instrument_navigation_basic_scenario.dart'; +import 'package:mazerunner/scenarios/auto_instrument_navigation_complex_defer_scenario.dart'; +import 'package:mazerunner/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart'; +import 'package:mazerunner/scenarios/auto_instrument_navigation_push_and_pop_scenario.dart'; import 'auto_instrument_app_starts_scenario.dart'; import 'dio_callback_cancel_span.dart'; @@ -71,4 +75,12 @@ final List> scenarios = [ ScenarioInfo('CustomAppVersionScenario', () => CustomAppVersionScenario()), ScenarioInfo('ManualSpanIsFirstClassFalseScenario', () => ManualSpanIsFirstClassFalseScenario()), + ScenarioInfo('AutoInstrumentNavigationBasicDeferScenario', + () => AutoInstrumentNavigationBasicDeferScenario()), + ScenarioInfo('AutoInstrumentNavigationComplexDeferScenario', + () => AutoInstrumentNavigationComplexDeferScenario()), + ScenarioInfo('AutoInstrumentNavigationNestedNavigationScenario', + () => AutoInstrumentNavigationNestedNavigationScenario()), + ScenarioInfo('AutoInstrumentNavigationPushAndPopScenario', + () => AutoInstrumentNavigationPushAndPopScenario()) ]; diff --git a/features/steps/flutter_steps.rb b/features/steps/flutter_steps.rb index 4a9026a..e7f2b51 100644 --- a/features/steps/flutter_steps.rb +++ b/features/steps/flutter_steps.rb @@ -193,3 +193,11 @@ def execute_command(action, scenario_name) Maze.check.true(span1['parentSpanId'] == nil); end + +Then('no span named {string} exists') do |span_name| + spans = spans_from_request_list(Maze::Server.list_for("traces")) + + spans_with_name = spans.find_all { |span| span['name'].eql?(span_name) } + + Maze.check.true(spans_with_name.length() == 0); +end diff --git a/packages/bugsnag-flutter-navigator-observer b/packages/bugsnag-flutter-navigator-observer index a275212..1e2459c 160000 --- a/packages/bugsnag-flutter-navigator-observer +++ b/packages/bugsnag-flutter-navigator-observer @@ -1 +1 @@ -Subproject commit a275212a0bcef3f641dd9cbf6437404db34479ec +Subproject commit 1e2459c5d0e4a90e4754d71e1c7485ae0ec44216 diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 50a0029..2158356 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -11,6 +11,10 @@ import 'bugsnag_flutter_performance.dart'; export 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart' show bugsnag_performance; export 'src/span.dart' show BugsnagPerformanceSpan; +export 'src/widgets/bugsnag_loading_indicator.dart' + show BugsnagLoadingIndicator; +export 'src/widgets/bugsnag_navigation_container.dart' + show BugsnagNavigationContainer; class BugsnagPerformance { BugsnagPerformance._internal(); diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 21b68ce..3545e70 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; -import 'package:bugsnag_flutter_performance/src/instrumentation/app_start_instrumentation.dart'; -import 'package:bugsnag_flutter_performance/src/instrumentation/navigation_instrumentation.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/app_start/app_start_instrumentation.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'package:bugsnag_flutter_performance/src/uploader/package_builder.dart'; @@ -202,7 +202,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { _updateSamplingProbabilityIfNeeded(force: true); }); BugsnagNavigatorObserverCallbacks.setup( - willShowNewRouteCallback: _navigationInstrumentation.willShowRoute, + didPushNewRouteCallback: _navigationInstrumentation.didPushNewRoute, + didReplaceRouteCallback: _navigationInstrumentation.didReplaceRoute, + didRemoveRouteCallback: _navigationInstrumentation.didRemoveRoute, + didPopRouteCallback: _navigationInstrumentation.didPopRoute, ); } diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start/app_start_instrumentation.dart similarity index 100% rename from packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start_instrumentation.dart rename to packages/bugsnag_flutter_performance/lib/src/instrumentation/app_start/app_start_instrumentation.dart diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart new file mode 100644 index 0000000..2a77892 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart @@ -0,0 +1,194 @@ +import 'package:bugsnag_flutter_performance/src/client.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation_node.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/widget_instrumentation_state.dart'; +import 'package:bugsnag_flutter_performance/src/span.dart'; +import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; + +abstract class NavigationInstrumentation { + void setEnabled(bool enabled); + void didPushNewRoute( + Route? route, + Route? previousRoute, + String? navigatorName, + ); + void didReplaceRoute( + Route? route, + Route? previousRoute, + String? navigatorName, + ); + void didRemoveRoute( + Route? shownRoute, + Route? removedRoute, + String? navigatorName, + ); + void didPopRoute( + Route? shownRoute, + Route? poppedRoute, + String? navigatorName, + ); +} + +class NavigationInstrumentationImpl implements NavigationInstrumentation { + final BugsnagPerformanceClient client; + final BugsnagClock clock; + + var _enabled = true; + + NavigationInstrumentationImpl({ + required this.client, + required this.clock, + }); + + @override + void setEnabled(bool enabled) { + _enabled = enabled; + } + + @override + void didPushNewRoute( + Route? route, + Route? previousRoute, + String? navigatorName, + ) { + _willShowRoute( + route, + previousRoute, + navigatorName, + 'push', + ); + } + + @override + void didReplaceRoute( + Route? route, + Route? previousRoute, + String? navigatorName, + ) { + _willShowRoute( + route, + previousRoute, + navigatorName, + 'replace', + ); + } + + @override + void didRemoveRoute( + Route? shownRoute, + Route? removedRoute, + String? navigatorName, + ) { + _willShowRoute( + shownRoute, + removedRoute, + navigatorName, + 'remove', + ); + } + + @override + void didPopRoute( + Route? shownRoute, + Route? poppedRoute, + String? navigatorName, + ) { + _willShowRoute( + shownRoute, + poppedRoute, + navigatorName, + 'pop', + ); + } + + void _willShowRoute( + Route? route, + Route? previousRoute, + String? navigatorName, + String triggeredBy, + ) { + final startTime = clock.now(); + final name = route?.settings.name; + if (!_enabled || route == null || name == null) { + return; + } + final node = route.navigator != null + ? WidgetInstrumentationNode.of(route.navigator!.context) + : appRootInstrumentationNode; + final state = WidgetInstrumentationState( + name: name, + startTime: startTime, + navigatorName: navigatorName, + ); + node.state = state; + _startNavigationSpan( + state, + triggeredBy: triggeredBy, + previousRoute: previousRoute?.settings.name, + ); + + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + if (!route.isCurrent) { + return; + } + if (node.isLoading()) { + node.addDidFinishLoadingCallback(() { + if (!route.isCurrent) { + return; + } + _endNavigationSpan(state, didFinishLoading: true); + }); + } else { + _endNavigationSpan(state, didFinishLoading: false); + } + }); + } + + void _startNavigationSpan( + WidgetInstrumentationState state, { + required String triggeredBy, + String? previousRoute, + }) { + if (!_enabled || state.viewLoadSpan != null) { + return; + } + final name = state.navigatorName != null + ? '[Navigation]${state.navigatorName}/${state.name}' + : '[Navigation]${state.name}'; + state.viewLoadSpan = client.startSpan( + name, + parentContext: state.nearestViewLoadSpan(), + startTime: state.startTime, + attributes: BugsnagPerformanceSpanAttributes( + category: 'navigation', + additionalAttributes: { + 'bugsnag.navigation.route': state.name, + 'bugsnag.navigation.navigator': state.navigatorName, + 'bugsnag.navigation.triggered_by': triggeredBy, + 'bugsnag.navigation.previous_route': previousRoute, + }, + ), + ); + } + + void _endNavigationSpan( + WidgetInstrumentationState state, { + bool didFinishLoading = false, + }) { + if (!_enabled) { + return; + } + final span = state.viewLoadSpan; + if (span is BugsnagPerformanceSpanImpl) { + span.attributes.setAttribute( + 'bugsnag.navigation.ended_by', + didFinishLoading ? 'loading_indicator' : 'frame_render', + ); + } + if (span != null) { + span.end(); + } + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation_node.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation_node.dart new file mode 100644 index 0000000..8dd6ef8 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation_node.dart @@ -0,0 +1,88 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/widget_instrumentation_state.dart'; +import 'package:bugsnag_flutter_performance/src/widgets/widget_instrumentation_node_provider.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; + +typedef DidFinishLoadingCallback = void Function(); +final appRootInstrumentationNode = WidgetInstrumentationNode(); + +class WidgetInstrumentationNode { + WidgetInstrumentationNode({ + this.state, + }); + WidgetInstrumentationState? state; + WidgetInstrumentationNode? _parent; + final List _didFinishLoadingCallbacks = []; + final Set> _loadingIndicators = {}; + final List _children = []; + + bool isLoading() { + return _loadingIndicators.where((element) => element.mounted).isNotEmpty; + } + + void addDidFinishLoadingCallback(DidFinishLoadingCallback callback) { + if (_didFinishLoadingCallbacks.isEmpty) { + _waitForNextFrameAndCheckIfLoading(); + } + _didFinishLoadingCallbacks.add(callback); + } + + void registerLoadingIndicator( + State loadingIndicator) { + _loadingIndicators.add(loadingIndicator); + if (_parent != null) { + _parent!.registerLoadingIndicator(loadingIndicator); + } + } + + void unregisterLoadingIndicator( + State loadingIndicator) { + _loadingIndicators.remove(loadingIndicator); + if (_parent != null) { + _parent!.unregisterLoadingIndicator(loadingIndicator); + } + } + + void addChild(WidgetInstrumentationNode node) { + node._parent = this; + _children.add(node); + } + + void removeChild(WidgetInstrumentationNode node) { + _children.remove(node); + } + + WidgetInstrumentationNode? child(String name) { + final namedIndex = + _children.indexWhere((element) => element.state?.name == name); + return namedIndex != -1 ? _children[namedIndex] : null; + } + + void dispose() { + _parent?.removeChild(this); + } + + void _waitForNextFrameAndCheckIfLoading() { + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + if (isLoading()) { + _waitForNextFrameAndCheckIfLoading(); + } else { + _didFinishLoading(); + } + }); + } + + void _didFinishLoading() { + for (final element in _didFinishLoadingCallbacks) { + element(); + } + _didFinishLoadingCallbacks.clear(); + } + + static WidgetInstrumentationNode of(BuildContext context) { + final widget = context.dependOnInheritedWidgetOfExactType< + NavigationInstrumentationNodeProvider>(); + return widget != null ? widget.node : appRootInstrumentationNode; + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/widget_instrumentation_state.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/widget_instrumentation_state.dart new file mode 100644 index 0000000..e3c1723 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/widget_instrumentation_state.dart @@ -0,0 +1,24 @@ +import 'package:bugsnag_flutter_performance/src/span.dart'; + +class WidgetInstrumentationState { + WidgetInstrumentationState({ + required this.name, + required this.startTime, + this.parent, + this.navigatorName, + }); + + final String name; + final WidgetInstrumentationState? parent; + final String? navigatorName; + + final DateTime startTime; + BugsnagPerformanceSpan? viewLoadSpan; + + BugsnagPerformanceSpan? nearestViewLoadSpan() { + if (viewLoadSpan != null && viewLoadSpan!.isOpen()) { + return viewLoadSpan; + } + return parent?.nearestViewLoadSpan(); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart deleted file mode 100644 index 140ff28..0000000 --- a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation_instrumentation.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:bugsnag_flutter_performance/src/client.dart'; -import 'package:bugsnag_flutter_performance/src/util/clock.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/widgets.dart'; - -abstract class NavigationInstrumentation { - void setEnabled(bool enabled); - void willShowRoute( - Route? route, - String? routeDescription, - ); -} - -class NavigationInstrumentationImpl implements NavigationInstrumentation { - final BugsnagPerformanceClient client; - late final BugsnagClock clock; - - var _enabled = true; - - NavigationInstrumentationImpl({ - required this.client, - required this.clock, - }); - - @override - void setEnabled(bool enabled) { - _enabled = enabled; - } - - @override - void willShowRoute(Route? route, String? routeDescription) { - if (!_enabled || routeDescription == null) { - return; - } - final startTime = clock.now(); - SchedulerBinding.instance.addPostFrameCallback((timeStamp) { - client - .startSpan('[Navigation]/$routeDescription', startTime: startTime) - .end(); - }); - } -} diff --git a/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_loading_indicator.dart b/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_loading_indicator.dart new file mode 100644 index 0000000..f874b12 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_loading_indicator.dart @@ -0,0 +1,36 @@ +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation_node.dart'; +import 'package:flutter/widgets.dart'; + +class BugsnagLoadingIndicator extends StatefulWidget { + const BugsnagLoadingIndicator({ + super.key, + this.child = const SizedBox.shrink(), + }); + final Widget child; + + @override + State createState() => + _BugsnagLoadingIndicatorState(); +} + +class _BugsnagLoadingIndicatorState extends State { + WidgetInstrumentationNode? _node; + + @override + Widget build(BuildContext context) { + return widget.child; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _node = WidgetInstrumentationNode.of(context); + _node?.registerLoadingIndicator(this); + } + + @override + void dispose() { + _node?.unregisterLoadingIndicator(this); + super.dispose(); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_navigation_container.dart b/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_navigation_container.dart new file mode 100644 index 0000000..8127f33 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/widgets/bugsnag_navigation_container.dart @@ -0,0 +1,48 @@ +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation_node.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/widget_instrumentation_state.dart'; +import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:bugsnag_flutter_performance/src/widgets/widget_instrumentation_node_provider.dart'; +import 'package:flutter/widgets.dart'; + +class BugsnagNavigationContainer extends StatefulWidget { + const BugsnagNavigationContainer({ + super.key, + this.name, + required this.child, + }); + final Widget child; + final String? name; + + @override + State createState() => + _BugsnagNavigationContainerState(); +} + +class _BugsnagNavigationContainerState + extends State { + WidgetInstrumentationNode? _currentNode; + + @override + Widget build(BuildContext context) { + _currentNode?.dispose(); + final parentNode = WidgetInstrumentationNode.of(context); + final newNode = WidgetInstrumentationNode( + state: WidgetInstrumentationState( + name: widget.name ?? 'NavigationContainer', + startTime: BugsnagClockImpl.instance.now(), + ), + ); + _currentNode = newNode; + parentNode.addChild(newNode); + return NavigationInstrumentationNodeProvider( + node: newNode, + child: widget.child, + ); + } + + @override + void dispose() { + _currentNode?.dispose(); + super.dispose(); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/widgets/widget_instrumentation_node_provider.dart b/packages/bugsnag_flutter_performance/lib/src/widgets/widget_instrumentation_node_provider.dart new file mode 100644 index 0000000..bbe5e21 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/widgets/widget_instrumentation_node_provider.dart @@ -0,0 +1,18 @@ +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation_node.dart'; +import 'package:flutter/widgets.dart'; + +class NavigationInstrumentationNodeProvider extends InheritedWidget { + const NavigationInstrumentationNodeProvider({ + super.key, + required this.node, + required super.child, + }); + + final WidgetInstrumentationNode node; + + @override + bool updateShouldNotify( + covariant NavigationInstrumentationNodeProvider oldWidget) { + return oldWidget.node != node; + } +} From cbc4670b08a2cdec1eec06f3099691212a480f39 Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Wed, 27 Mar 2024 11:17:14 +0000 Subject: [PATCH 47/66] docs(pubspec): pre-release updates --- .../bugsnag_flutter_performance/pubspec.yaml | 42 +++---------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 47f1095..f54bd05 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -1,7 +1,10 @@ name: bugsnag_flutter_performance -description: A new Flutter plugin project. +description: BugSnag performance monitoring tool for Flutter apps version: 0.0.1 -homepage: +homepage: https://www.bugsnag.com/ +documentation: https://docs.bugsnag.com/performance/flutter/ +repository: https://github.com/bugsnag/bugsnag-flutter +issue_tracker: https://github.com/bugsnag/bugsnag-flutter/issues # TODO: Remove once all dependencies are published publish_to: none @@ -27,10 +30,6 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) @@ -49,34 +48,3 @@ flutter: pluginClass: BugsnagFlutterPerformancePlugin ios: pluginClass: BugsnagFlutterPerformancePlugin - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages From 92f3606c56ed0f02b2e657108f0e783c00b56c68 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 27 Mar 2024 16:03:52 +0100 Subject: [PATCH 48/66] PLAT-11846 validate api key (#47) --- .../lib/bugsnag_flutter_performance.dart | 17 +++++++++++++++++ .../test/src/client_test.dart | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 2158356..3b16b32 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -16,6 +16,14 @@ export 'src/widgets/bugsnag_loading_indicator.dart' export 'src/widgets/bugsnag_navigation_container.dart' show BugsnagNavigationContainer; +class InvalidBugsnagApiKeyException implements Exception { + String message; + InvalidBugsnagApiKeyException(this.message); + + @override + String toString() => "InvalidApiKeyException: $message"; +} + class BugsnagPerformance { BugsnagPerformance._internal(); @@ -31,6 +39,7 @@ class BugsnagPerformance { List? enabledReleaseStages, String? appVersion, }) { + _validateApiKey(apiKey); return _client.start( apiKey: apiKey, endpoint: endpoint, @@ -41,6 +50,14 @@ class BugsnagPerformance { ); } + void _validateApiKey(String apiKey) { + final RegExp regExp = RegExp(r'^[0-9a-fA-F]{32}$'); + if(apiKey.isEmpty || !regExp.hasMatch(apiKey)) + { + throw InvalidBugsnagApiKeyException("Invalid configuration. apiKey should be a 32-character hexademical string, got $apiKey"); + } + } + BugsnagPerformanceSpan startSpan(String name, {DateTime? startTime, BugsnagPerformanceSpanContext? parentContext, diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index 37113d4..149b83c 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -9,6 +9,7 @@ import 'package:bugsnag_flutter_performance/src/uploader/retry_queue_builder.dar import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; class MockRetryQueue implements RetryQueue { @override @@ -79,6 +80,11 @@ void main() { expect(span.endTime, isNull); }); }); + group('invalidApiKey', () { + test('should throw exception when invalid API key is used', () async { + expect(() => bugsnag_performance.start(apiKey: "invalid"), throwsA(isA())); + }); + }); group('onAppBackgrounded', () { test('should cancel spans when app background event triggers', () async { await client.start(apiKey: apiKey, endpoint: endpoint); From 84c76e5873090494ff93d198a17cbe1c010f9afe Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 28 Mar 2024 10:57:14 +0100 Subject: [PATCH 49/66] bump minimum flutter version to 3.10 (#49) --- packages/bugsnag_flutter_performance/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 47f1095..c670779 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -7,7 +7,7 @@ publish_to: none environment: sdk: '>=3.0.0 <4.0.0' - flutter: ">=3.3.0" + flutter: ">=3.10.0" dependencies: crypto: ^3.0.3 From 5c7ea1dd8cba84b0f35d98139de2d4215703af43 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 28 Mar 2024 11:35:44 +0100 Subject: [PATCH 50/66] PLAT-11823 allow access to current span context (#45) --- .../get_current_context_scenario.dart | 21 +++++++++++++++++++ .../lib/scenarios/scenarios.dart | 2 ++ features/manual_span.feature | 8 +++++++ .../lib/bugsnag_flutter_performance.dart | 4 ++++ .../lib/src/client.dart | 7 +++++-- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/get_current_context_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/get_current_context_scenario.dart b/features/fixture_resources/lib/scenarios/get_current_context_scenario.dart new file mode 100644 index 0000000..bf1d0ee --- /dev/null +++ b/features/fixture_resources/lib/scenarios/get_current_context_scenario.dart @@ -0,0 +1,21 @@ + + +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class GetCurrentContextScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(3); + if(bugsnag_performance.getCurrentSpanContext() == null) { + doSimpleSpan('part 1: null'); + } + + final span1 = bugsnag_performance.startSpan('context'); + if(bugsnag_performance.getCurrentSpanContext() != null) { + doSimpleSpan('part 2: not null'); + span1.end(); + } + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index bbff8de..e473ee3 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -29,6 +29,7 @@ import 'custom_enabled_release_stage_scenario.dart'; import 'disable_custom_release_stage_scenario.dart'; import 'custom_app_version_scenario.dart'; import 'manual_span_isfirstclass_false_scenario.dart'; +import 'get_current_context_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -75,6 +76,7 @@ final List> scenarios = [ ScenarioInfo('CustomAppVersionScenario', () => CustomAppVersionScenario()), ScenarioInfo('ManualSpanIsFirstClassFalseScenario', () => ManualSpanIsFirstClassFalseScenario()), + ScenarioInfo('GetCurrentContextScenario', () => GetCurrentContextScenario()), ScenarioInfo('AutoInstrumentNavigationBasicDeferScenario', () => AutoInstrumentNavigationBasicDeferScenario()), ScenarioInfo('AutoInstrumentNavigationComplexDeferScenario', diff --git a/features/manual_span.feature b/features/manual_span.feature index fd01efd..cf3eb70 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -29,3 +29,11 @@ Feature: Manual Spans * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * every span field "name" equals "MaxBatchAgeScenario" + + Scenario: Get Current Context + When I run "GetCurrentContextScenario" + And I wait for 3 spans + * the span named "part 1: null" exists + * the span named "part 2: not null" exists + * the span named "context" is the parent of the span named "part 2: not null" + diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 3b16b32..2a2bbf8 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -93,6 +93,10 @@ class BugsnagPerformance { dynamic networkInstrumentation(dynamic data) { return _client.networkInstrumentation(data); } + + BugsnagPerformanceSpanContext? getCurrentSpanContext() { + return _client.getCurrentSpanContext(); + } } // ignore: non_constant_identifier_names diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 3545e70..75f884a 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -49,6 +49,8 @@ abstract class BugsnagPerformanceClient { BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod); + BugsnagPerformanceSpanContext? getCurrentSpanContext(); + dynamic networkInstrumentation(dynamic); } @@ -137,7 +139,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { bool? makeCurrentContext = true, BugsnagPerformanceSpanAttributes? attributes, }) { - final parent = parentContext ?? getCurrentContext(); + final parent = parentContext ?? getCurrentSpanContext(); final span = BugsnagPerformanceSpanImpl( name: name, @@ -288,7 +290,8 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } } - BugsnagPerformanceSpanContext? getCurrentContext() { + @override + BugsnagPerformanceSpanContext? getCurrentSpanContext() { return _getContextStack()?.getCurrentContext(); } From a7883bb78d11d428e8b817c702274e1b022eda85 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 28 Mar 2024 15:13:40 +0100 Subject: [PATCH 51/66] sub mods update --- packages/bugsnag-flutter-dart-io-http-client | 2 +- packages/bugsnag-flutter-http-client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bugsnag-flutter-dart-io-http-client b/packages/bugsnag-flutter-dart-io-http-client index cd67b71..01aefeb 160000 --- a/packages/bugsnag-flutter-dart-io-http-client +++ b/packages/bugsnag-flutter-dart-io-http-client @@ -1 +1 @@ -Subproject commit cd67b716a8aa51f18fcea785e7a68ce24060895b +Subproject commit 01aefeb070c333402269de31f79bf79725d20fa0 diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index 2d1ad19..c7f75f5 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit 2d1ad192b91844562a029777d49ee746545cb125 +Subproject commit c7f75f57763ec5899ecae2235964d55985f891a7 From 6b0ff8c4663b6280ba9be14bfc4033663075eb44 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Wed, 3 Apr 2024 22:09:13 +0200 Subject: [PATCH 52/66] pre release fixes (#50) --- .../check_network_callback_type_scenario.dart | 23 +++++++++++++++++++ .../scenarios/custom_span_time_scenario.dart | 16 +++++++++++++ .../lib/scenarios/scenarios.dart | 9 +++++++- .../span_with_no_parent_scenario.dart | 19 +++++++++++++++ features/manual_span.feature | 14 +++++++++++ features/network_spans.feature | 8 +++++++ .../lib/bugsnag_flutter_performance.dart | 10 ++++---- .../lib/src/bugsnag_network_request_info.dart | 3 ++- .../lib/src/client.dart | 10 +++++--- .../lib/src/span.dart | 4 +++- .../lib/src/span_context.dart | 16 +++++++++++++ .../test/src/client_test.dart | 3 ++- 12 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/custom_span_time_scenario.dart create mode 100644 features/fixture_resources/lib/scenarios/span_with_no_parent_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart b/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart new file mode 100644 index 0000000..a7445f4 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart @@ -0,0 +1,23 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http; +import '../main.dart'; +import 'scenario.dart'; + +class CheckNetworkCallbackTypeScenario extends Scenario { + @override + Future run() async { + bugsnag_performance.setExtraConfig("instrumentAppStart", false); + String type = "not-set"; + await bugsnag_performance.start( + apiKey: '12312312312312312312312312312312', + endpoint: Uri.parse('${FixtureConfig.MAZE_HOST}/traces'), + networkRequestCallback: (info) { + type = info.type!; + return info; + }); + setMaxBatchSize(2); + http.addSubscriber(bugsnag_performance.networkInstrumentation); + await http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); + doSimpleSpan(type); + } +} diff --git a/features/fixture_resources/lib/scenarios/custom_span_time_scenario.dart b/features/fixture_resources/lib/scenarios/custom_span_time_scenario.dart new file mode 100644 index 0000000..426fc03 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/custom_span_time_scenario.dart @@ -0,0 +1,16 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class CustomSpanTimeScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(1); + bugsnag_performance + .startSpan( + 'custom-time', + startTime: DateTime.fromMicrosecondsSinceEpoch(473385600000000, isUtc: true) + ) + .end(endTime: DateTime.fromMicrosecondsSinceEpoch(504921600000000, isUtc: true)); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index e473ee3..a99f39e 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -30,6 +30,9 @@ import 'disable_custom_release_stage_scenario.dart'; import 'custom_app_version_scenario.dart'; import 'manual_span_isfirstclass_false_scenario.dart'; import 'get_current_context_scenario.dart'; +import 'check_network_callback_type_scenario.dart'; +import 'custom_span_time_scenario.dart'; +import 'span_with_no_parent_scenario.dart'; import 'scenario.dart'; class ScenarioInfo { @@ -84,5 +87,9 @@ final List> scenarios = [ ScenarioInfo('AutoInstrumentNavigationNestedNavigationScenario', () => AutoInstrumentNavigationNestedNavigationScenario()), ScenarioInfo('AutoInstrumentNavigationPushAndPopScenario', - () => AutoInstrumentNavigationPushAndPopScenario()) + () => AutoInstrumentNavigationPushAndPopScenario()), + ScenarioInfo('CheckNetworkCallbackTypeScenario', + () => CheckNetworkCallbackTypeScenario()), + ScenarioInfo('CustomSpanTimeScenario', () => CustomSpanTimeScenario()), + ScenarioInfo('SpanWithNoParentScenario', () => SpanWithNoParentScenario()), ]; diff --git a/features/fixture_resources/lib/scenarios/span_with_no_parent_scenario.dart b/features/fixture_resources/lib/scenarios/span_with_no_parent_scenario.dart new file mode 100644 index 0000000..1a593e5 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/span_with_no_parent_scenario.dart @@ -0,0 +1,19 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class SpanWithNoParentScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(2); + + final parent = bugsnag_performance.startSpan('parent'); + + bugsnag_performance.startSpan( + 'no-parent', + parentContext: BugsnagPerformanceSpanContext.invalid, + ).end(); + + parent.end(); + } +} diff --git a/features/manual_span.feature b/features/manual_span.feature index cf3eb70..3ac1f46 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -37,3 +37,17 @@ Feature: Manual Spans * the span named "part 2: not null" exists * the span named "context" is the parent of the span named "part 2: not null" + Scenario: Custom timings + When I run "CustomSpanTimeScenario" + And I wait for 1 span + * every span field "startTimeUnixNano" equals "473385600000000000" + * every span field "endTimeUnixNano" equals "504921600000000000" + + Scenario: Span With No Parent + When I run "SpanWithNoParentScenario" + And I wait for 2 spans + * the span named "parent" exists + * the span named "no-parent" exists + * the span named "no-parent" has no parent + + diff --git a/features/network_spans.feature b/features/network_spans.feature index 8ed0999..f1686e8 100644 --- a/features/network_spans.feature +++ b/features/network_spans.feature @@ -136,4 +136,12 @@ Feature: Network Spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.status_code" is greater than 0 * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" integer attribute "http.response_content_length" is greater than 0 + Scenario: Network callback type + When I run "CheckNetworkCallbackTypeScenario" + And I wait for 2 spans + Then the trace "Content-Type" header equals "application/json" + * the span named "GET" exists + + + \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 2a2bbf8..c4e93c8 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -5,11 +5,12 @@ import 'dart:async'; import 'package:bugsnag_flutter_performance/src/bugsnag_network_request_info.dart'; import 'package:bugsnag_flutter_performance/src/client.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; -import 'package:bugsnag_flutter_performance/src/span_context.dart'; import 'bugsnag_flutter_performance.dart'; export 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart' show bugsnag_performance; +export 'package:bugsnag_flutter_performance/src/span_context.dart' + show BugsnagPerformanceSpanContext; export 'src/span.dart' show BugsnagPerformanceSpan; export 'src/widgets/bugsnag_loading_indicator.dart' show BugsnagLoadingIndicator; @@ -26,7 +27,6 @@ class InvalidBugsnagApiKeyException implements Exception { class BugsnagPerformance { BugsnagPerformance._internal(); - static final BugsnagPerformanceClientImpl _client = BugsnagPerformanceClientImpl(); @@ -52,9 +52,9 @@ class BugsnagPerformance { void _validateApiKey(String apiKey) { final RegExp regExp = RegExp(r'^[0-9a-fA-F]{32}$'); - if(apiKey.isEmpty || !regExp.hasMatch(apiKey)) - { - throw InvalidBugsnagApiKeyException("Invalid configuration. apiKey should be a 32-character hexademical string, got $apiKey"); + if (apiKey.isEmpty || !regExp.hasMatch(apiKey)) { + throw InvalidBugsnagApiKeyException( + "Invalid configuration. apiKey should be a 32-character hexademical string, got $apiKey"); } } diff --git a/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart b/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart index 2d80afc..d29325f 100644 --- a/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart +++ b/packages/bugsnag_flutter_performance/lib/src/bugsnag_network_request_info.dart @@ -1,4 +1,5 @@ class BugsnagNetworkRequestInfo { String? url; - BugsnagNetworkRequestInfo({this.url}); + String? type; + BugsnagNetworkRequestInfo({this.url, this.type}); } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 75f884a..508c516 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -139,7 +139,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { bool? makeCurrentContext = true, BugsnagPerformanceSpanAttributes? attributes, }) { - final parent = parentContext ?? getCurrentSpanContext(); + final BugsnagPerformanceSpanContext? parent = + parentContext != BugsnagPerformanceSpanContext.invalid + ? parentContext ?? getCurrentSpanContext() + : null; final span = BugsnagPerformanceSpanImpl( name: name, @@ -311,9 +314,10 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { if (status == "started") { String url = data["url"]; + final String method = data["http_method"]; if (_networkRequestCallback != null) { BugsnagNetworkRequestInfo requestInfo = - BugsnagNetworkRequestInfo(url: url); + BugsnagNetworkRequestInfo(url: url, type: method); BugsnagNetworkRequestInfo? modifiedRequestInfo = _networkRequestCallback!(requestInfo); if (modifiedRequestInfo?.url == null || @@ -322,7 +326,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } url = modifiedRequestInfo.url!; } - final span = startNetworkSpan(url, data['http_method']); + final span = startNetworkSpan(url, method); _networkSpans[requestId] = span; } else if (status == "complete") { final span = _networkSpans[requestId]; diff --git a/packages/bugsnag_flutter_performance/lib/src/span.dart b/packages/bugsnag_flutter_performance/lib/src/span.dart index 8914421..9ddf9ac 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span.dart @@ -19,6 +19,7 @@ abstract class BugsnagPerformanceSpan implements BugsnagPerformanceSpanContext { int? requestContentLength, int? responseContentLength, bool cancelled = false, + DateTime? endTime, }); dynamic toJson(); } @@ -60,11 +61,12 @@ class BugsnagPerformanceSpanImpl int? requestContentLength, int? responseContentLength, bool cancelled = false, + DateTime? endTime, }) { if (!isOpen()) { return; } - endTime = clock.now(); + this.endTime = endTime ?? clock.now(); if (cancelled) { onCanceled(this); return; diff --git a/packages/bugsnag_flutter_performance/lib/src/span_context.dart b/packages/bugsnag_flutter_performance/lib/src/span_context.dart index 5fcbe9d..f4394ff 100644 --- a/packages/bugsnag_flutter_performance/lib/src/span_context.dart +++ b/packages/bugsnag_flutter_performance/lib/src/span_context.dart @@ -29,7 +29,23 @@ class BugsnagPerformanceSpanContextStackImpl } abstract class BugsnagPerformanceSpanContext { + static final BugsnagPerformanceSpanContext invalid = _InvalidSpanContext(); + TraceId get traceId; SpanId get spanId; bool isOpen(); } + +class _InvalidSpanContext implements BugsnagPerformanceSpanContext { + @override + final TraceId traceId = BigInt.zero; + @override + final SpanId spanId = BigInt.zero; + + _InvalidSpanContext(); + + @override + bool isOpen() { + return false; + } +} diff --git a/packages/bugsnag_flutter_performance/test/src/client_test.dart b/packages/bugsnag_flutter_performance/test/src/client_test.dart index 149b83c..bb7386c 100644 --- a/packages/bugsnag_flutter_performance/test/src/client_test.dart +++ b/packages/bugsnag_flutter_performance/test/src/client_test.dart @@ -82,7 +82,8 @@ void main() { }); group('invalidApiKey', () { test('should throw exception when invalid API key is used', () async { - expect(() => bugsnag_performance.start(apiKey: "invalid"), throwsA(isA())); + expect(() => bugsnag_performance.start(apiKey: "invalid"), + throwsA(isA())); }); }); group('onAppBackgrounded', () { From 3b615a667cfdef406218abbb7b0f56bcf9e52c0a Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 4 Apr 2024 11:54:15 +0200 Subject: [PATCH 53/66] PLAT-11907 rename http wrapper clients to allow drop in replacement (#51) --- .../lib/scenarios/check_network_callback_type_scenario.dart | 2 +- .../fixture_resources/lib/scenarios/dart_io_get_scenario.dart | 2 +- .../lib/scenarios/dio_callback_cancel_span.dart | 2 +- .../lib/scenarios/dio_callback_edit_scenario.dart | 2 +- features/fixture_resources/lib/scenarios/dio_get_scenario.dart | 2 +- features/fixture_resources/lib/scenarios/dio_post_scenario.dart | 2 +- .../lib/scenarios/http_callback_cancel_span.dart | 2 +- .../lib/scenarios/http_callback_edit_scenario.dart | 2 +- .../fixture_resources/lib/scenarios/http_post_scenario.dart | 2 +- packages/bugsnag-flutter-dart-io-http-client | 2 +- packages/bugsnag-flutter-http-client | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart b/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart index a7445f4..e76b672 100644 --- a/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart +++ b/features/fixture_resources/lib/scenarios/check_network_callback_type_scenario.dart @@ -17,7 +17,7 @@ class CheckNetworkCallbackTypeScenario extends Scenario { }); setMaxBatchSize(2); http.addSubscriber(bugsnag_performance.networkInstrumentation); - await http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); + await http.Client().get(FixtureConfig.MAZE_HOST); doSimpleSpan(type); } } diff --git a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart index 4845a97..5bdfc58 100644 --- a/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dart_io_get_scenario.dart @@ -12,7 +12,7 @@ class DartIoGetScenario extends Scenario { await startBugsnag(); setMaxBatchSize(1); dartIo.addSubscriber(bugsnag_performance.networkInstrumentation); - final client = dartIo.BugsnagHttpClient(); + final client = dartIo.HttpClient(); HttpClientRequest request = await client.getUrl(FixtureConfig.MAZE_HOST); await request.close(); } diff --git a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart index 9ffbfe6..6424be0 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_cancel_span.dart @@ -22,7 +22,7 @@ class DIOCallbackCancelSpanScenario extends Scenario { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { - return dart_io.BugsnagHttpClient(); + return dart_io.HttpClient(); }, ); dio.get(FixtureConfig.MAZE_HOST.toString()); diff --git a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart index 9d458d1..1e74cbf 100644 --- a/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_callback_edit_scenario.dart @@ -24,7 +24,7 @@ class DIOCallbackEditScenario extends Scenario { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { - return dart_io.BugsnagHttpClient(); + return dart_io.HttpClient(); }, ); diff --git a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart index 6196e81..d0101fa 100644 --- a/features/fixture_resources/lib/scenarios/dio_get_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_get_scenario.dart @@ -16,7 +16,7 @@ class DIOGetScenario extends Scenario { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { - return dart_io.BugsnagHttpClient(); + return dart_io.HttpClient(); }, ); diff --git a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart index bd0e356..1dc3bfa 100644 --- a/features/fixture_resources/lib/scenarios/dio_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/dio_post_scenario.dart @@ -16,7 +16,7 @@ class DIOPostScenario extends Scenario { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { - return dart_io.BugsnagHttpClient(); + return dart_io.HttpClient(); }, ); diff --git a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart index a05f98f..b3d276f 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_cancel_span.dart @@ -15,7 +15,7 @@ class HttpCallbackCancelSpanScenario extends Scenario { }); setMaxBatchSize(1); http.addSubscriber(bugsnag_performance.networkInstrumentation); - http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); + http.Client().get(FixtureConfig.MAZE_HOST); await Future.delayed(const Duration(seconds: 10)); bugsnag_performance.startSpan('HttpCallbackCancelSpanScenario').end(); } diff --git a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart index 5f53620..96ccb0e 100644 --- a/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_callback_edit_scenario.dart @@ -16,6 +16,6 @@ class HttpCallbackEditScenario extends Scenario { }); setMaxBatchSize(1); http.addSubscriber(bugsnag_performance.networkInstrumentation); - http.BugSnagHttpClient().get(FixtureConfig.MAZE_HOST); + http.Client().get(FixtureConfig.MAZE_HOST); } } diff --git a/features/fixture_resources/lib/scenarios/http_post_scenario.dart b/features/fixture_resources/lib/scenarios/http_post_scenario.dart index 21970f9..76375d6 100644 --- a/features/fixture_resources/lib/scenarios/http_post_scenario.dart +++ b/features/fixture_resources/lib/scenarios/http_post_scenario.dart @@ -9,7 +9,7 @@ class HttpPostScenario extends Scenario { await startBugsnag(); setMaxBatchSize(1); http.addSubscriber(bugsnag_performance.networkInstrumentation); - http.BugSnagHttpClient() + http.Client() .post(FixtureConfig.MAZE_HOST, body: {"key": "value"}); } } diff --git a/packages/bugsnag-flutter-dart-io-http-client b/packages/bugsnag-flutter-dart-io-http-client index 01aefeb..3a0bb2b 160000 --- a/packages/bugsnag-flutter-dart-io-http-client +++ b/packages/bugsnag-flutter-dart-io-http-client @@ -1 +1 @@ -Subproject commit 01aefeb070c333402269de31f79bf79725d20fa0 +Subproject commit 3a0bb2b53a9ce0b5e1fbf3274bdb49f6d2515d9e diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index c7f75f5..66e22c0 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit c7f75f57763ec5899ecae2235964d55985f891a7 +Subproject commit 66e22c03d431fa92b15b6a7f3c70c9578185d750 From 54428c69567e0858c7ef59444c5e0dc0201f2689 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 4 Apr 2024 14:51:54 +0200 Subject: [PATCH 54/66] sub mod update --- packages/bugsnag-flutter-dart-io-http-client | 2 +- packages/bugsnag-flutter-http-client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bugsnag-flutter-dart-io-http-client b/packages/bugsnag-flutter-dart-io-http-client index 3a0bb2b..e4cfd22 160000 --- a/packages/bugsnag-flutter-dart-io-http-client +++ b/packages/bugsnag-flutter-dart-io-http-client @@ -1 +1 @@ -Subproject commit 3a0bb2b53a9ce0b5e1fbf3274bdb49f6d2515d9e +Subproject commit e4cfd2273207405f01f77237c3540e9287657145 diff --git a/packages/bugsnag-flutter-http-client b/packages/bugsnag-flutter-http-client index 66e22c0..ded4cc5 160000 --- a/packages/bugsnag-flutter-http-client +++ b/packages/bugsnag-flutter-http-client @@ -1 +1 @@ -Subproject commit 66e22c03d431fa92b15b6a7f3c70c9578185d750 +Subproject commit ded4cc58b2835ccefd09989c38ac5aed6c706436 From 5b71664fcb36c28fa822a642347a23915e8f1aab Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:23:52 +0200 Subject: [PATCH 55/66] Made navigator observer a part of Bugsnag Performance (#52) * Renamed navigator observer to BugsnagPerformanceNavigatorObserver and made it a part of Bugsnag Perfromance * Removed mentions of breadcrumbs in comments for BugsnagPerformanceNavigatorObserver --------- Co-authored-by: Robert --- .gitmodules | 3 - Gemfile.lock | 99 ++++++++++++++++++- features/fixture_resources/lib/main.dart | 4 +- ...navigation_nested_navigation_scenario.dart | 4 +- features/scripts/generate_fixture.sh | 2 - packages/bugsnag-flutter-navigator-observer | 1 - .../lib/bugsnag_flutter_performance.dart | 2 + .../lib/src/client.dart | 5 +- ...ugsnag_performance_navigator_observer.dart | 59 +++++++++++ ...formance_navigator_observer_callbacks.dart | 92 +++++++++++++++++ .../bugsnag_flutter_performance/pubspec.yaml | 2 - 11 files changed, 257 insertions(+), 16 deletions(-) delete mode 160000 packages/bugsnag-flutter-navigator-observer create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer.dart create mode 100644 packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer_callbacks.dart diff --git a/.gitmodules b/.gitmodules index 71e2aeb..66c8fc5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "packages/bugsnag-flutter-http-client"] path = packages/bugsnag-flutter-http-client url = git@github.com:bugsnag/bugsnag-flutter-http-client.git -[submodule "packages/bugsnag-flutter-navigator-observer"] - path = packages/bugsnag-flutter-navigator-observer - url=git@github.com:bugsnag/bugsnag-flutter-navigator-observer.git [submodule "packages/bugsnag-flutter-dart-io-http-client"] path = packages/bugsnag-flutter-dart-io-http-client url = git@github.com:bugsnag/bugsnag-flutter-dart-io-http-client.git diff --git a/Gemfile.lock b/Gemfile.lock index 364ab8c..db9a2b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,25 @@ GEM remote: https://rubygems.org/ specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.1.3.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) appium_lib (12.0.1) appium_lib_core (~> 5.0) nokogiri (~> 1.8, >= 1.8.1) @@ -8,6 +27,9 @@ GEM appium_lib_core (5.4.0) faye-websocket (~> 0.11.0) selenium-webdriver (~> 4.2, < 4.6) + atomos (0.1.3) + base64 (0.2.0) + bigdecimal (3.1.7) bugsnag (6.26.0) concurrent-ruby (~> 1.0) bugsnag-maze-runner (8.13.2) @@ -29,7 +51,47 @@ GEM webrick (~> 1.7.0) builder (3.2.4) childprocess (4.1.0) + claide (1.1.0) + cocoapods (1.15.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.15.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) concurrent-ruby (1.2.2) + connection_pool (2.4.1) cucumber (7.1.0) builder (~> 3.2, >= 3.2.4) cucumber-core (~> 10.1, >= 10.1.0) @@ -64,14 +126,25 @@ GEM curb (0.9.11) diff-lcs (1.5.0) dogstatsd-ruby (5.5.0) + drb (2.2.1) ecma-re-validator (0.4.0) regexp_parser (~> 2.2) + escape (0.0.4) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) faye-websocket (0.11.3) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) ffi (1.16.3) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) hana (1.3.7) + httpclient (2.8.3) + i18n (1.14.4) + concurrent-ruby (~> 1.0) + json (2.7.2) json_schemer (0.2.25) ecma-re-validator (~> 0.3) hana (~> 1.3) @@ -81,17 +154,28 @@ GEM mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.1003) + mini_portile2 (2.8.5) + minitest (5.22.3) + molinillo (0.8.0) multi_test (0.1.2) - nokogiri (1.15.5-x86_64-darwin) + mutex_m (0.2.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.2.0) + nokogiri (1.15.5) + mini_portile2 (~> 2.8.2) racc (~> 1.4) optimist (3.0.1) os (1.0.1) power_assert (2.0.3) + public_suffix (4.0.7) racc (1.7.3) rack (2.2.8) rake (12.3.3) regexp_parser (2.8.2) rexml (3.2.6) + ruby-macho (2.5.1) rubyzip (2.3.2) selenium-webdriver (4.5.0) childprocess (>= 0.5, < 5.0) @@ -105,6 +189,10 @@ GEM test-unit (3.5.9) power_assert tomlrb (2.0.3) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext unf_ext (0.0.9.1) @@ -114,13 +202,22 @@ GEM websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) PLATFORMS x86_64-darwin-20 x86_64-darwin-21 + x86_64-darwin-23 DEPENDENCIES bugsnag-maze-runner (~> 8.13.2) + cocoapods BUNDLED WITH 2.4.8 diff --git a/features/fixture_resources/lib/main.dart b/features/fixture_resources/lib/main.dart index dc120f2..516580c 100644 --- a/features/fixture_resources/lib/main.dart +++ b/features/fixture_resources/lib/main.dart @@ -4,11 +4,11 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; import 'package:flutter/material.dart'; import 'package:native_flutter_proxy/custom_proxy.dart'; import 'package:native_flutter_proxy/native_proxy_reader.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:bugsnag_navigator_observer/bugsnag_flutter_navigator_observer.dart'; import 'package:http/http.dart' as http; import 'scenarios/scenario.dart'; @@ -93,7 +93,7 @@ class MazeRunnerFlutterApp extends StatelessWidget { theme: ThemeData( primaryColor: const Color.fromARGB(255, 73, 73, 227), ), - navigatorObservers: [BugsnagNavigatorObserver()], + navigatorObservers: [BugsnagPerformanceNavigatorObserver()], home: FutureBuilder( future: _getMazeRunnerUrl(), builder: (_, mazerunnerUrl) { diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart index a91cd5e..1803414 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_navigation_nested_navigation_scenario.dart @@ -1,5 +1,5 @@ import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; -import 'package:bugsnag_navigator_observer/bugsnag_flutter_navigator_observer.dart'; + import 'package:flutter/material.dart'; import 'package:mazerunner/main.dart'; @@ -22,7 +22,7 @@ class AutoInstrumentNavigationNestedNavigationScenario extends Scenario { return BugsnagNavigationContainer( child: Navigator( observers: [ - BugsnagNavigatorObserver( + BugsnagPerformanceNavigatorObserver( navigatorName: "nested_scenario_child_navigator") ], pages: [ diff --git a/features/scripts/generate_fixture.sh b/features/scripts/generate_fixture.sh index 3cebd76..1ac37ca 100755 --- a/features/scripts/generate_fixture.sh +++ b/features/scripts/generate_fixture.sh @@ -8,7 +8,6 @@ fi FIXTURE_LOCATION=features/fixtures/mazerunner PACKAGE_PATH="$(pwd)/packages/bugsnag_flutter_performance" -NAVIGATOR_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-navigator-observer" HTTP_WRAPPER_PACKAGE_PATH="$(pwd)/packages/bugsnag-flutter-http-client" @@ -49,7 +48,6 @@ $FLUTTER_BIN create $FIXTURE_LOCATION --org com.bugsnag --platforms=ios,android echo "Add dependencies" $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_flutter_performance:{'path':'$PACKAGE_PATH'}" -$FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" "bugsnag_navigator_observer:{'path':'$NAVIGATOR_PACKAGE_PATH'}" $FLUTTER_BIN pub add --directory="$FIXTURE_LOCATION" path_provider diff --git a/packages/bugsnag-flutter-navigator-observer b/packages/bugsnag-flutter-navigator-observer deleted file mode 160000 index 1e2459c..0000000 --- a/packages/bugsnag-flutter-navigator-observer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1e2459c5d0e4a90e4754d71e1c7485ae0ec44216 diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index c4e93c8..f7d8c79 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -16,6 +16,8 @@ export 'src/widgets/bugsnag_loading_indicator.dart' show BugsnagLoadingIndicator; export 'src/widgets/bugsnag_navigation_container.dart' show BugsnagNavigationContainer; +export 'src/instrumentation/navigation/bugsnag_performance_navigator_observer.dart' + show BugsnagPerformanceNavigatorObserver; class InvalidBugsnagApiKeyException implements Exception { String message; diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 508c516..e373638 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:bugsnag_flutter_performance/src/extensions/bugsnag_lifecycle_listener.dart'; import 'package:bugsnag_flutter_performance/src/extensions/resource_attributes.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/app_start/app_start_instrumentation.dart'; +import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/bugsnag_performance_navigator_observer_callbacks.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation.dart'; import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/span_context.dart'; @@ -16,8 +17,6 @@ import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; -// ignore: implementation_imports -import 'package:bugsnag_navigator_observer/src/bugsnag_navigator_observer_callbacks.dart'; import 'package:flutter/widgets.dart'; import 'bugsnag_network_request_info.dart'; @@ -206,7 +205,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { (timer) { _updateSamplingProbabilityIfNeeded(force: true); }); - BugsnagNavigatorObserverCallbacks.setup( + BugsnagPerformanceNavigatorObserverCallbacks.setup( didPushNewRouteCallback: _navigationInstrumentation.didPushNewRoute, didReplaceRouteCallback: _navigationInstrumentation.didReplaceRoute, didRemoveRouteCallback: _navigationInstrumentation.didRemoveRoute, diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer.dart new file mode 100644 index 0000000..6f5e163 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer.dart @@ -0,0 +1,59 @@ +import 'package:flutter/widgets.dart'; + +import 'bugsnag_performance_navigator_observer_callbacks.dart'; + +class BugsnagPerformanceNavigatorObserver extends NavigatorObserver { + final String? navigatorName; + + /// Create and configure a `BugsnagPerformanceNavigatorObserver` to listen for navigation + /// events. + /// + /// Typically you will configure this in you `MaterialApp`, `CupertinoApp` + /// or `Navigator`: + /// ```dart + /// return MaterialApp( + /// navigatorObservers: [BugsnagPerformanceNavigatorObserver()], + /// initialRoute: '/', + /// routes: { + /// '/': (context) => const AppHomeWidget(), + /// ``` + BugsnagPerformanceNavigatorObserver({ + this.navigatorName, + }); + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + callbacks.didReplaceRoute( + newRoute: newRoute, + previousRoute: oldRoute, + navigatorName: navigatorName, + ); + } + + @override + void didRemove(Route route, Route? previousRoute) { + callbacks.didRemoveRoute( + newRoute: previousRoute, + previousRoute: route, + navigatorName: navigatorName, + ); + } + + @override + void didPop(Route route, Route? previousRoute) { + callbacks.didPopRoute( + newRoute: previousRoute, + previousRoute: route, + navigatorName: navigatorName, + ); + } + + @override + void didPush(Route route, Route? previousRoute) { + callbacks.didPushNewRoute( + newRoute: route, + previousRoute: previousRoute, + navigatorName: navigatorName, + ); + } +} diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer_callbacks.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer_callbacks.dart new file mode 100644 index 0000000..328b588 --- /dev/null +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/bugsnag_performance_navigator_observer_callbacks.dart @@ -0,0 +1,92 @@ +import 'package:flutter/widgets.dart'; + +typedef WillShowNewRouteCallback = Function( + Route? newRoute, + Route? previousRoute, + String? navigatorName, +); + +class BugsnagPerformanceNavigatorObserverCallbacks { + WillShowNewRouteCallback? _didPushNewRouteCallback; + WillShowNewRouteCallback? _didReplaceRouteCallback; + WillShowNewRouteCallback? _didRemoveRouteCallback; + WillShowNewRouteCallback? _didPopRouteCallback; + + void didPushNewRoute({ + Route? newRoute, + Route? previousRoute, + String? navigatorName, + }) { + if (_didPushNewRouteCallback != null) { + _didPushNewRouteCallback!( + newRoute, + previousRoute, + navigatorName, + ); + } + } + + void didReplaceRoute({ + Route? newRoute, + Route? previousRoute, + String? navigatorName, + }) { + if (_didReplaceRouteCallback != null) { + _didReplaceRouteCallback!( + newRoute, + previousRoute, + navigatorName, + ); + } + } + + void didRemoveRoute({ + Route? newRoute, + Route? previousRoute, + String? navigatorName, + }) { + if (_didRemoveRouteCallback != null) { + _didRemoveRouteCallback!( + newRoute, + previousRoute, + navigatorName, + ); + } + } + + void didPopRoute({ + Route? newRoute, + Route? previousRoute, + String? navigatorName, + }) { + if (_didPopRouteCallback != null) { + _didPopRouteCallback!( + newRoute, + previousRoute, + navigatorName, + ); + } + } + + static setup({ + WillShowNewRouteCallback? didPushNewRouteCallback, + WillShowNewRouteCallback? didReplaceRouteCallback, + WillShowNewRouteCallback? didRemoveRouteCallback, + WillShowNewRouteCallback? didPopRouteCallback, + }) { + if (didPushNewRouteCallback != null) { + callbacks._didPushNewRouteCallback = didPushNewRouteCallback; + } + if (didReplaceRouteCallback != null) { + callbacks._didReplaceRouteCallback = didReplaceRouteCallback; + } + if (didRemoveRouteCallback != null) { + callbacks._didRemoveRouteCallback = didRemoveRouteCallback; + } + if (didPopRouteCallback != null) { + callbacks._didPopRouteCallback = didPopRouteCallback; + } + } +} + +final callbacks = BugsnagPerformanceNavigatorObserverCallbacks(); diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 0f24737..7572f0a 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -22,8 +22,6 @@ dependencies: path_provider: ^2.0.2 uuid: ^4.0.0 connectivity_plus: ^5.0.2 - bugsnag_navigator_observer: - path: ../bugsnag-flutter-navigator-observer dev_dependencies: flutter_test: From b3775f442161edf29e7e8d7b347b815f3399e42c Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Tue, 9 Apr 2024 09:06:50 +0200 Subject: [PATCH 56/66] make instrumentAppStart a public config parameter. (#53) --- examples/bugsnag_performance_example/lib/main.dart | 1 + .../scenarios/auto_instrument_app_starts_scenario.dart | 3 ++- .../lib/bugsnag_flutter_performance.dart | 2 ++ packages/bugsnag_flutter_performance/lib/src/client.dart | 5 ++++- .../lib/src/configuration.dart | 8 +++++--- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index f2f7129..2eb9278 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -6,6 +6,7 @@ const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { bugsnag_performance.start(apiKey: apiKey); + bugsnag_performance.start(apiKey: apiKey, instrumentAppStart: true); http.addSubscriber(bugsnag_performance.networkInstrumentation); bugsnag_performance.measureRunApp(() async => runApp(const MainApp())); } diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart index f19836a..bbf9f6e 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart @@ -10,7 +10,8 @@ class AutoInstrumentAppStartsScenario extends Scenario { bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); bugsnag_performance.start( apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces'), + instrumentAppStarts: true); bugsnag_performance.measureRunApp(() async => await Duration(seconds: 1)); setMaxBatchSize(4); } diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index f7d8c79..c10a466 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -40,6 +40,7 @@ class BugsnagPerformance { String? releaseStage, List? enabledReleaseStages, String? appVersion, + bool? instrumentAppStarts, }) { _validateApiKey(apiKey); return _client.start( @@ -49,6 +50,7 @@ class BugsnagPerformance { releaseStage: releaseStage, enabledReleaseStages: enabledReleaseStages, appVersion: appVersion, + instrumentAppStarts: instrumentAppStarts, ); } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index e373638..eb95b81 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -34,6 +34,7 @@ abstract class BugsnagPerformanceClient { String? releaseStage, List? enabledReleaseStages, String? appVersion, + bool? instrumentAppStarts }); Future measureRunApp(FutureOr Function() runApp); @@ -101,6 +102,7 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { String? releaseStage, List? enabledReleaseStages, String? appVersion, + bool? instrumentAppStarts }) async { WidgetsFlutterBinding.ensureInitialized(); _networkRequestCallback = networkRequestCallback; @@ -110,13 +112,14 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { releaseStage: releaseStage ?? getDeploymentEnvironment(), enabledReleaseStages: enabledReleaseStages, appVersion: appVersion, + instrumentAppStarts: instrumentAppStarts, ); _packageBuilder.setConfig(configuration); _initialExtraConfig.forEach((key, value) { setExtraConfig(key, value); }); _appStartInstrumentation - .setEnabled(configuration?.instrumentAppStart ?? false); + .setEnabled(configuration?.instrumentAppStarts ?? false); _navigationInstrumentation .setEnabled(configuration?.instrumentNavigation ?? false); _setup(); diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index 8bbaff2..c2da6d7 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -4,14 +4,16 @@ class BugsnagPerformanceConfiguration { this.endpoint, this.releaseStage, this.enabledReleaseStages, - this.appVersion}); + this.appVersion, + this.instrumentAppStarts, + }); String? apiKey; Uri? endpoint; int maxBatchSize = 100; int maxBatchAge = 60 * 1000; // milliseconds int probabilityRequestsPause = 30000; int probabilityValueExpireTime = 24 * 3600 * 1000; - bool instrumentAppStart = true; + bool? instrumentAppStarts; bool instrumentNavigation = true; String? releaseStage; List? enabledReleaseStages; @@ -35,7 +37,7 @@ class BugsnagPerformanceConfiguration { probabilityValueExpireTime = value; break; case 'instrumentAppStart': - instrumentAppStart = value; + instrumentAppStarts = value; break; case 'instrumentNavigation': instrumentNavigation = value; From cd1b63bfb5079e89a9cf7a2d617733a9036195d2 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Tue, 9 Apr 2024 11:36:43 +0200 Subject: [PATCH 57/66] Revert "make instrumentAppStart a public config parameter. (#53)" This reverts commit b3775f442161edf29e7e8d7b347b815f3399e42c. --- examples/bugsnag_performance_example/lib/main.dart | 1 - .../scenarios/auto_instrument_app_starts_scenario.dart | 3 +-- .../lib/bugsnag_flutter_performance.dart | 2 -- packages/bugsnag_flutter_performance/lib/src/client.dart | 5 +---- .../lib/src/configuration.dart | 8 +++----- 5 files changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/bugsnag_performance_example/lib/main.dart b/examples/bugsnag_performance_example/lib/main.dart index 2eb9278..f2f7129 100644 --- a/examples/bugsnag_performance_example/lib/main.dart +++ b/examples/bugsnag_performance_example/lib/main.dart @@ -6,7 +6,6 @@ const apiKey = 'YOUR_API_KEY_HERE'; Future main() async { bugsnag_performance.start(apiKey: apiKey); - bugsnag_performance.start(apiKey: apiKey, instrumentAppStart: true); http.addSubscriber(bugsnag_performance.networkInstrumentation); bugsnag_performance.measureRunApp(() async => runApp(const MainApp())); } diff --git a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart index bbf9f6e..f19836a 100644 --- a/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart +++ b/features/fixture_resources/lib/scenarios/auto_instrument_app_starts_scenario.dart @@ -10,8 +10,7 @@ class AutoInstrumentAppStartsScenario extends Scenario { bugsnag_performance.setExtraConfig("probabilityValueExpireTime", 1000); bugsnag_performance.start( apiKey: '12312312312312312312312312312312', - endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces'), - instrumentAppStarts: true); + endpoint: Uri.parse(FixtureConfig.MAZE_HOST.toString() + '/traces')); bugsnag_performance.measureRunApp(() async => await Duration(seconds: 1)); setMaxBatchSize(4); } diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index c10a466..f7d8c79 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -40,7 +40,6 @@ class BugsnagPerformance { String? releaseStage, List? enabledReleaseStages, String? appVersion, - bool? instrumentAppStarts, }) { _validateApiKey(apiKey); return _client.start( @@ -50,7 +49,6 @@ class BugsnagPerformance { releaseStage: releaseStage, enabledReleaseStages: enabledReleaseStages, appVersion: appVersion, - instrumentAppStarts: instrumentAppStarts, ); } diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index eb95b81..e373638 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -34,7 +34,6 @@ abstract class BugsnagPerformanceClient { String? releaseStage, List? enabledReleaseStages, String? appVersion, - bool? instrumentAppStarts }); Future measureRunApp(FutureOr Function() runApp); @@ -102,7 +101,6 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { String? releaseStage, List? enabledReleaseStages, String? appVersion, - bool? instrumentAppStarts }) async { WidgetsFlutterBinding.ensureInitialized(); _networkRequestCallback = networkRequestCallback; @@ -112,14 +110,13 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { releaseStage: releaseStage ?? getDeploymentEnvironment(), enabledReleaseStages: enabledReleaseStages, appVersion: appVersion, - instrumentAppStarts: instrumentAppStarts, ); _packageBuilder.setConfig(configuration); _initialExtraConfig.forEach((key, value) { setExtraConfig(key, value); }); _appStartInstrumentation - .setEnabled(configuration?.instrumentAppStarts ?? false); + .setEnabled(configuration?.instrumentAppStart ?? false); _navigationInstrumentation .setEnabled(configuration?.instrumentNavigation ?? false); _setup(); diff --git a/packages/bugsnag_flutter_performance/lib/src/configuration.dart b/packages/bugsnag_flutter_performance/lib/src/configuration.dart index c2da6d7..8bbaff2 100644 --- a/packages/bugsnag_flutter_performance/lib/src/configuration.dart +++ b/packages/bugsnag_flutter_performance/lib/src/configuration.dart @@ -4,16 +4,14 @@ class BugsnagPerformanceConfiguration { this.endpoint, this.releaseStage, this.enabledReleaseStages, - this.appVersion, - this.instrumentAppStarts, - }); + this.appVersion}); String? apiKey; Uri? endpoint; int maxBatchSize = 100; int maxBatchAge = 60 * 1000; // milliseconds int probabilityRequestsPause = 30000; int probabilityValueExpireTime = 24 * 3600 * 1000; - bool? instrumentAppStarts; + bool instrumentAppStart = true; bool instrumentNavigation = true; String? releaseStage; List? enabledReleaseStages; @@ -37,7 +35,7 @@ class BugsnagPerformanceConfiguration { probabilityValueExpireTime = value; break; case 'instrumentAppStart': - instrumentAppStarts = value; + instrumentAppStart = value; break; case 'instrumentNavigation': instrumentNavigation = value; From 498696ce69f4ed95f16bdc0780152fc645a7dc6e Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 11 Apr 2024 09:51:36 +0200 Subject: [PATCH 58/66] remove navigator dep from example project --- examples/bugsnag_performance_example/pubspec.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/examples/bugsnag_performance_example/pubspec.yaml index 0ab24eb..331eb4d 100644 --- a/examples/bugsnag_performance_example/pubspec.yaml +++ b/examples/bugsnag_performance_example/pubspec.yaml @@ -27,14 +27,6 @@ dependencies: # the parent directory to use the current plugin's version. path: ../../packages/bugsnag_flutter_performance - bugsnag_navigator_observer: - # When depending on this package from a real application you should use: - # flutter: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../../packages/bugsnag-flutter-navigator-observer - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 From 1987394b0b2b0a10fd7fe9bd20f975f880683821 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:32:11 +0200 Subject: [PATCH 59/66] Made start of BugsnagFlutterPerformance client on web a no op (#55) * Made start of BugsnagFlutterPerformance client on web a no op * Fixed compile errors --------- Co-authored-by: Robert --- .../bugsnag_flutter_performance/lib/src/client.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index e373638..396f1b4 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -17,6 +17,7 @@ import 'package:bugsnag_flutter_performance/src/uploader/span_batch.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader.dart'; import 'package:bugsnag_flutter_performance/src/uploader/uploader_client.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'bugsnag_network_request_info.dart'; @@ -102,6 +103,11 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { List? enabledReleaseStages, String? appVersion, }) async { + if (!_isEnabledOnCurrentPlatform()) { + _appStartInstrumentation.setEnabled(false); + _navigationInstrumentation.setEnabled(false); + return; + } WidgetsFlutterBinding.ensureInitialized(); _networkRequestCallback = networkRequestCallback; configuration = BugsnagPerformanceConfiguration( @@ -351,4 +357,8 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { } _potentiallyOpenSpans.clear(); } + + bool _isEnabledOnCurrentPlatform() { + return !kIsWeb; + } } From f19e5ae3ebb355932b27163c722eb64ac17bab1b Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:32:36 +0200 Subject: [PATCH 60/66] Added startNavigationSpan method to public interface (#56) * Added startNavigationSpan method to public interface * E2E tests fixture changes * Removed unused import * Added a missing param --------- Co-authored-by: Robert --- .../manual_navigation_span_scenario.dart | 17 +++++++++ .../lib/scenarios/scenarios.dart | 3 ++ features/manual_span.feature | 18 ++++++++++ .../lib/bugsnag_flutter_performance.dart | 15 ++++++++ .../lib/src/client.dart | 36 +++++++++++++++++++ .../navigation_instrumentation.dart | 20 +++-------- 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart diff --git a/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart new file mode 100644 index 0000000..ff20117 --- /dev/null +++ b/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart @@ -0,0 +1,17 @@ +import 'package:bugsnag_flutter_performance/bugsnag_flutter_performance.dart'; +import 'scenario.dart'; + +class ManualNavigationSpanScenario extends Scenario { + @override + Future run() async { + await startBugsnag(); + setMaxBatchSize(1); + bugsnag_performance + .startNavigationSpan( + routeName: 'navigationScenarioRoute', + navigatorName: 'customNavigator', + previousRoute: 'navigationScenarioPreviousRoute', + ) + .end(); + } +} diff --git a/features/fixture_resources/lib/scenarios/scenarios.dart b/features/fixture_resources/lib/scenarios/scenarios.dart index a99f39e..4e69279 100644 --- a/features/fixture_resources/lib/scenarios/scenarios.dart +++ b/features/fixture_resources/lib/scenarios/scenarios.dart @@ -8,6 +8,7 @@ import 'auto_instrument_app_starts_scenario.dart'; import 'dio_callback_cancel_span.dart'; import 'dio_callback_edit_scenario.dart'; import 'initial_p_scenario.dart'; +import 'manual_navigation_span_scenario.dart'; import 'manual_span_scenario.dart'; import 'probability_expiry_scenario.dart'; import 'start_sdk_default.dart'; @@ -92,4 +93,6 @@ final List> scenarios = [ () => CheckNetworkCallbackTypeScenario()), ScenarioInfo('CustomSpanTimeScenario', () => CustomSpanTimeScenario()), ScenarioInfo('SpanWithNoParentScenario', () => SpanWithNoParentScenario()), + ScenarioInfo( + 'ManualNavigationSpanScenario', () => ManualNavigationSpanScenario()), ]; diff --git a/features/manual_span.feature b/features/manual_span.feature index 3ac1f46..4b4fe55 100644 --- a/features/manual_span.feature +++ b/features/manual_span.feature @@ -50,4 +50,22 @@ Feature: Manual Spans * the span named "no-parent" exists * the span named "no-parent" has no parent +Scenario: Manual Navigation Span + When I run "ManualNavigationSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * every span field "name" equals "[Navigation]customNavigator/navigationScenarioRoute" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span string attribute "bugsnag.span.category" equals "navigation" + * every span string attribute "bugsnag.navigation.route" equals "navigationScenarioRoute" + * every span string attribute "bugsnag.navigation.navigator" equals "customNavigator" + * every span string attribute "bugsnag.navigation.triggered_by" equals "manual" + * every span string attribute "bugsnag.navigation.previous_route" equals "navigationScenarioPreviousRoute" + * a span double attribute "bugsnag.sampling.p" equals 1.0 + diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index f7d8c79..7d65b54 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -81,6 +81,21 @@ class BugsnagPerformance { return _client.startNetworkSpan(url, httpMethod.toUpperCase()); } + BugsnagPerformanceSpan startNavigationSpan({ + required String routeName, + String? navigatorName, + String? previousRoute, + BugsnagPerformanceSpanContext? parentContext, + }) { + return _client.startNavigationSpan( + routeName: routeName, + triggeredBy: 'manual', + navigatorName: navigatorName, + previousRoute: previousRoute, + parentContext: parentContext, + ); + } + Future measureRunApp( FutureOr Function() runApp, ) async { diff --git a/packages/bugsnag_flutter_performance/lib/src/client.dart b/packages/bugsnag_flutter_performance/lib/src/client.dart index 396f1b4..5ca77d5 100644 --- a/packages/bugsnag_flutter_performance/lib/src/client.dart +++ b/packages/bugsnag_flutter_performance/lib/src/client.dart @@ -49,6 +49,15 @@ abstract class BugsnagPerformanceClient { BugsnagPerformanceSpan startNetworkSpan(String url, String httpMethod); + BugsnagPerformanceSpan startNavigationSpan({ + required String routeName, + required String triggeredBy, + String? navigatorName, + String? previousRoute, + BugsnagPerformanceSpanContext? parentContext, + DateTime? startTime, + }); + BugsnagPerformanceSpanContext? getCurrentSpanContext(); dynamic networkInstrumentation(dynamic); @@ -311,6 +320,33 @@ class BugsnagPerformanceClientImpl implements BugsnagPerformanceClient { category: "network", httpMethod: httpMethod, url: url)); } + @override + BugsnagPerformanceSpan startNavigationSpan({ + required String routeName, + required String triggeredBy, + String? navigatorName, + String? previousRoute, + BugsnagPerformanceSpanContext? parentContext, + DateTime? startTime, + }) { + final name = navigatorName != null + ? '[Navigation]$navigatorName/$routeName' + : '[Navigation]$routeName'; + return startSpan( + name, + parentContext: parentContext, + attributes: BugsnagPerformanceSpanAttributes( + category: 'navigation', + additionalAttributes: { + 'bugsnag.navigation.route': routeName, + 'bugsnag.navigation.navigator': navigatorName, + 'bugsnag.navigation.triggered_by': triggeredBy, + 'bugsnag.navigation.previous_route': previousRoute, + }, + ), + ); + } + @override dynamic networkInstrumentation(dynamic data) { if (data is! Map) return true; diff --git a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart index 2a77892..7b54f27 100644 --- a/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart +++ b/packages/bugsnag_flutter_performance/lib/src/instrumentation/navigation/navigation_instrumentation.dart @@ -2,7 +2,6 @@ import 'package:bugsnag_flutter_performance/src/client.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/navigation_instrumentation_node.dart'; import 'package:bugsnag_flutter_performance/src/instrumentation/navigation/widget_instrumentation_state.dart'; import 'package:bugsnag_flutter_performance/src/span.dart'; -import 'package:bugsnag_flutter_performance/src/span_attributes.dart'; import 'package:bugsnag_flutter_performance/src/util/clock.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; @@ -154,22 +153,13 @@ class NavigationInstrumentationImpl implements NavigationInstrumentation { if (!_enabled || state.viewLoadSpan != null) { return; } - final name = state.navigatorName != null - ? '[Navigation]${state.navigatorName}/${state.name}' - : '[Navigation]${state.name}'; - state.viewLoadSpan = client.startSpan( - name, + state.viewLoadSpan = client.startNavigationSpan( + routeName: state.name, + navigatorName: state.navigatorName, + previousRoute: previousRoute, parentContext: state.nearestViewLoadSpan(), startTime: state.startTime, - attributes: BugsnagPerformanceSpanAttributes( - category: 'navigation', - additionalAttributes: { - 'bugsnag.navigation.route': state.name, - 'bugsnag.navigation.navigator': state.navigatorName, - 'bugsnag.navigation.triggered_by': triggeredBy, - 'bugsnag.navigation.previous_route': previousRoute, - }, - ), + triggeredBy: triggeredBy, ); } From e64d8138c609be6bc9baaace150b558947eb96da Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 11 Apr 2024 11:51:46 +0200 Subject: [PATCH 61/66] rename example dir (#59) --- Makefile | 12 ++++++------ .../bugsnag_performance_example/.gitignore | 0 .../bugsnag_performance_example/.metadata | 0 .../analysis_options.yaml | 0 .../bugsnag_performance_example/android/.gitignore | 0 .../android/app/build.gradle | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../bugsnag_performance_example/MainActivity.kt | 0 .../src/main/res/drawable-v21/launch_background.xml | 0 .../app/src/main/res/drawable/launch_background.xml | 0 .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../android/app/src/main/res/values/styles.xml | 0 .../android/app/src/profile/AndroidManifest.xml | 0 .../android/build.gradle | 0 .../android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/settings.gradle | 0 .../bugsnag_performance_example/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../ios/Flutter/Debug.xcconfig | 0 .../ios/Flutter/Release.xcconfig | 0 .../bugsnag_performance_example/ios/Podfile | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../ios/Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../AppIcon.appiconset/Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../Assets.xcassets/LaunchImage.imageset/README.md | 0 .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../ios/RunnerTests/RunnerTests.swift | 0 .../bugsnag_performance_example/lib/main.dart | 0 .../bugsnag_performance_example/pubspec.yaml | 0 65 files changed, 6 insertions(+), 6 deletions(-) rename {examples => example}/bugsnag_performance_example/.gitignore (100%) rename {examples => example}/bugsnag_performance_example/.metadata (100%) rename {examples => example}/bugsnag_performance_example/analysis_options.yaml (100%) rename {examples => example}/bugsnag_performance_example/android/.gitignore (100%) rename {examples => example}/bugsnag_performance_example/android/app/build.gradle (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/main/res/values/styles.xml (100%) rename {examples => example}/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml (100%) rename {examples => example}/bugsnag_performance_example/android/build.gradle (100%) rename {examples => example}/bugsnag_performance_example/android/gradle.properties (100%) rename {examples => example}/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {examples => example}/bugsnag_performance_example/android/settings.gradle (100%) rename {examples => example}/bugsnag_performance_example/ios/.gitignore (100%) rename {examples => example}/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist (100%) rename {examples => example}/bugsnag_performance_example/ios/Flutter/Debug.xcconfig (100%) rename {examples => example}/bugsnag_performance_example/ios/Flutter/Release.xcconfig (100%) rename {examples => example}/bugsnag_performance_example/ios/Podfile (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/AppDelegate.swift (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Info.plist (100%) rename {examples => example}/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h (100%) rename {examples => example}/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift (100%) rename {examples => example}/bugsnag_performance_example/lib/main.dart (100%) rename {examples => example}/bugsnag_performance_example/pubspec.yaml (100%) diff --git a/Makefile b/Makefile index e514611..fba627b 100644 --- a/Makefile +++ b/Makefile @@ -2,18 +2,18 @@ FLUTTER_BIN?=flutter all: format build lint test -.PHONY: clean build bump aar examples/bugsnag_performance_example test format lint e2e_android_local e2e_ios_local +.PHONY: clean build bump aar example/bugsnag_performance_example test format lint e2e_android_local e2e_ios_local clean: - cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) clean --suppress-analytics - cd examples/bugsnag_performance_example && $(FLUTTER_BIN) clean --suppress-analytics && \ + cd package/bugsnag_flutter_performance && $(FLUTTER_BIN) clean --suppress-analytics + cd example/bugsnag_performance_example && $(FLUTTER_BIN) clean --suppress-analytics && \ rm -rf .idea bugsnag_flutter_performance_example.iml \ ios/{Pods,.symlinks,Podfile.lock} \ ios/{Runner.xcworkspace,Runner.xcodeproj,Runner.xcodeproj/project.xcworkspace}/xcuserdata \ android/{.idea,.gradle,gradlew,gradlew.bat,local.properties,bugsnag_flutter_performance_example_android.iml} rm -rf staging -build: aar examples/bugsnag_performance_example +build: aar example/bugsnag_performance_example bump: ifeq ($(VERSION),) @@ -60,7 +60,7 @@ endif aar: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) build aar --suppress-analytics -examples/bugsnag_performance_example: +example/bugsnag_performance_example: cd $@ && $(FLUTTER_BIN) pub get cd $@ && $(FLUTTER_BIN) build apk --suppress-analytics cd $@ && $(FLUTTER_BIN) build ios --no-codesign --suppress-analytics --no-tree-shake-icons @@ -69,7 +69,7 @@ test: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) test -r expanded --suppress-analytics format: - dart format packages/bugsnag_flutter_performance examples features/fixture_resources/lib + dart format packages/bugsnag_flutter_performance example features/fixture_resources/lib lint: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) analyze --suppress-analytics diff --git a/examples/bugsnag_performance_example/.gitignore b/example/bugsnag_performance_example/.gitignore similarity index 100% rename from examples/bugsnag_performance_example/.gitignore rename to example/bugsnag_performance_example/.gitignore diff --git a/examples/bugsnag_performance_example/.metadata b/example/bugsnag_performance_example/.metadata similarity index 100% rename from examples/bugsnag_performance_example/.metadata rename to example/bugsnag_performance_example/.metadata diff --git a/examples/bugsnag_performance_example/analysis_options.yaml b/example/bugsnag_performance_example/analysis_options.yaml similarity index 100% rename from examples/bugsnag_performance_example/analysis_options.yaml rename to example/bugsnag_performance_example/analysis_options.yaml diff --git a/examples/bugsnag_performance_example/android/.gitignore b/example/bugsnag_performance_example/android/.gitignore similarity index 100% rename from examples/bugsnag_performance_example/android/.gitignore rename to example/bugsnag_performance_example/android/.gitignore diff --git a/examples/bugsnag_performance_example/android/app/build.gradle b/example/bugsnag_performance_example/android/app/build.gradle similarity index 100% rename from examples/bugsnag_performance_example/android/app/build.gradle rename to example/bugsnag_performance_example/android/app/build.gradle diff --git a/examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml b/example/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml rename to example/bugsnag_performance_example/android/app/src/debug/AndroidManifest.xml diff --git a/examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml b/example/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml rename to example/bugsnag_performance_example/android/app/src/main/AndroidManifest.xml diff --git a/examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt b/example/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt rename to example/bugsnag_performance_example/android/app/src/main/kotlin/com/example/bugsnag_performance_example/MainActivity.kt diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml rename to example/bugsnag_performance_example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml b/example/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml rename to example/bugsnag_performance_example/android/app/src/main/res/drawable/launch_background.xml diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to example/bugsnag_performance_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to example/bugsnag_performance_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to example/bugsnag_performance_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to example/bugsnag_performance_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to example/bugsnag_performance_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml b/example/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml rename to example/bugsnag_performance_example/android/app/src/main/res/values-night/styles.xml diff --git a/examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml b/example/bugsnag_performance_example/android/app/src/main/res/values/styles.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/main/res/values/styles.xml rename to example/bugsnag_performance_example/android/app/src/main/res/values/styles.xml diff --git a/examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml b/example/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from examples/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml rename to example/bugsnag_performance_example/android/app/src/profile/AndroidManifest.xml diff --git a/examples/bugsnag_performance_example/android/build.gradle b/example/bugsnag_performance_example/android/build.gradle similarity index 100% rename from examples/bugsnag_performance_example/android/build.gradle rename to example/bugsnag_performance_example/android/build.gradle diff --git a/examples/bugsnag_performance_example/android/gradle.properties b/example/bugsnag_performance_example/android/gradle.properties similarity index 100% rename from examples/bugsnag_performance_example/android/gradle.properties rename to example/bugsnag_performance_example/android/gradle.properties diff --git a/examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties b/example/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from examples/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties rename to example/bugsnag_performance_example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/examples/bugsnag_performance_example/android/settings.gradle b/example/bugsnag_performance_example/android/settings.gradle similarity index 100% rename from examples/bugsnag_performance_example/android/settings.gradle rename to example/bugsnag_performance_example/android/settings.gradle diff --git a/examples/bugsnag_performance_example/ios/.gitignore b/example/bugsnag_performance_example/ios/.gitignore similarity index 100% rename from examples/bugsnag_performance_example/ios/.gitignore rename to example/bugsnag_performance_example/ios/.gitignore diff --git a/examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist b/example/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from examples/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist rename to example/bugsnag_performance_example/ios/Flutter/AppFrameworkInfo.plist diff --git a/examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig b/example/bugsnag_performance_example/ios/Flutter/Debug.xcconfig similarity index 100% rename from examples/bugsnag_performance_example/ios/Flutter/Debug.xcconfig rename to example/bugsnag_performance_example/ios/Flutter/Debug.xcconfig diff --git a/examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig b/example/bugsnag_performance_example/ios/Flutter/Release.xcconfig similarity index 100% rename from examples/bugsnag_performance_example/ios/Flutter/Release.xcconfig rename to example/bugsnag_performance_example/ios/Flutter/Release.xcconfig diff --git a/examples/bugsnag_performance_example/ios/Podfile b/example/bugsnag_performance_example/ios/Podfile similarity index 100% rename from examples/bugsnag_performance_example/ios/Podfile rename to example/bugsnag_performance_example/ios/Podfile diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj b/example/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj rename to example/bugsnag_performance_example/ios/Runner.xcodeproj/project.pbxproj diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/bugsnag_performance_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/bugsnag_performance_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to example/bugsnag_performance_example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/bugsnag_performance_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift b/example/bugsnag_performance_example/ios/Runner/AppDelegate.swift similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/AppDelegate.swift rename to example/bugsnag_performance_example/ios/Runner/AppDelegate.swift diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to example/bugsnag_performance_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to example/bugsnag_performance_example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard b/example/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard rename to example/bugsnag_performance_example/ios/Runner/Base.lproj/Main.storyboard diff --git a/examples/bugsnag_performance_example/ios/Runner/Info.plist b/example/bugsnag_performance_example/ios/Runner/Info.plist similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Info.plist rename to example/bugsnag_performance_example/ios/Runner/Info.plist diff --git a/examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h b/example/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from examples/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h rename to example/bugsnag_performance_example/ios/Runner/Runner-Bridging-Header.h diff --git a/examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift b/example/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from examples/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift rename to example/bugsnag_performance_example/ios/RunnerTests/RunnerTests.swift diff --git a/examples/bugsnag_performance_example/lib/main.dart b/example/bugsnag_performance_example/lib/main.dart similarity index 100% rename from examples/bugsnag_performance_example/lib/main.dart rename to example/bugsnag_performance_example/lib/main.dart diff --git a/examples/bugsnag_performance_example/pubspec.yaml b/example/bugsnag_performance_example/pubspec.yaml similarity index 100% rename from examples/bugsnag_performance_example/pubspec.yaml rename to example/bugsnag_performance_example/pubspec.yaml From 734a758d211157e9c416f30501eed825978faef0 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 11 Apr 2024 11:52:03 +0200 Subject: [PATCH 62/66] remove unused native modules (#57) --- .../android/.gitignore | 9 - .../android/build.gradle | 70 -------- .../android/gradle.properties | 1 - .../android/gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - .../android/gradlew | 160 ------------------ .../android/gradlew.bat | 90 ---------- .../android/settings.gradle | 1 - .../android/src/main/AndroidManifest.xml | 3 - .../BugsnagFlutterPerformancePlugin.kt | 33 ---- .../BugsnagFlutterPerformancePluginTest.kt | 27 --- .../ios/.gitignore | 38 ----- .../ios/Assets/.gitkeep | 0 .../BugsnagFlutterPerformancePlugin.swift | 19 --- .../ios/Resources/PrivacyInfo.xcprivacy | 45 ----- .../ios/bugsnag_flutter_performance.podspec | 23 --- ...ag_flutter_performance_method_channel.dart | 19 --- ...lutter_performance_platform_interface.dart | 30 ---- .../bugsnag_flutter_performance/pubspec.yaml | 8 +- 19 files changed, 1 insertion(+), 580 deletions(-) delete mode 100644 packages/bugsnag_flutter_performance/android/.gitignore delete mode 100644 packages/bugsnag_flutter_performance/android/build.gradle delete mode 100644 packages/bugsnag_flutter_performance/android/gradle.properties delete mode 100755 packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 packages/bugsnag_flutter_performance/android/gradlew delete mode 100755 packages/bugsnag_flutter_performance/android/gradlew.bat delete mode 100644 packages/bugsnag_flutter_performance/android/settings.gradle delete mode 100644 packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml delete mode 100644 packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt delete mode 100644 packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt delete mode 100644 packages/bugsnag_flutter_performance/ios/.gitignore delete mode 100644 packages/bugsnag_flutter_performance/ios/Assets/.gitkeep delete mode 100644 packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift delete mode 100644 packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy delete mode 100644 packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec delete mode 100644 packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart delete mode 100644 packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart diff --git a/packages/bugsnag_flutter_performance/android/.gitignore b/packages/bugsnag_flutter_performance/android/.gitignore deleted file mode 100644 index 161bdcd..0000000 --- a/packages/bugsnag_flutter_performance/android/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.cxx diff --git a/packages/bugsnag_flutter_performance/android/build.gradle b/packages/bugsnag_flutter_performance/android/build.gradle deleted file mode 100644 index bc14fc3..0000000 --- a/packages/bugsnag_flutter_performance/android/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -group 'com.example.bugsnag_flutter_performance' -version '1.0-SNAPSHOT' - -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion 31 - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - test.java.srcDirs += 'src/test/kotlin' - } - - defaultConfig { - minSdkVersion 16 - } - - dependencies { - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation 'org.mockito:mockito-core:5.0.0' - } - - buildTypes { - profile { - initWith debug - } - } - - testOptions { - unitTests.all { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} - showStandardStreams = true - } - } - } -} diff --git a/packages/bugsnag_flutter_performance/android/gradle.properties b/packages/bugsnag_flutter_performance/android/gradle.properties deleted file mode 100644 index 5bac8ac..0000000 --- a/packages/bugsnag_flutter_performance/android/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -android.useAndroidX=true diff --git a/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar b/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100755 index 13372aef5e24af05341d49695ee84e5f9b594659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53636 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ⅅ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 H-+%o-(+5aQ diff --git a/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties b/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3c472b9..0000000 --- a/packages/bugsnag_flutter_performance/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/bugsnag_flutter_performance/android/gradlew b/packages/bugsnag_flutter_performance/android/gradlew deleted file mode 100755 index 9d82f78..0000000 --- a/packages/bugsnag_flutter_performance/android/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/packages/bugsnag_flutter_performance/android/gradlew.bat b/packages/bugsnag_flutter_performance/android/gradlew.bat deleted file mode 100755 index aec9973..0000000 --- a/packages/bugsnag_flutter_performance/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/packages/bugsnag_flutter_performance/android/settings.gradle b/packages/bugsnag_flutter_performance/android/settings.gradle deleted file mode 100644 index 602593a..0000000 --- a/packages/bugsnag_flutter_performance/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'bugsnag_flutter_performance' diff --git a/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml b/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml deleted file mode 100644 index bb5472c..0000000 --- a/packages/bugsnag_flutter_performance/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt b/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt deleted file mode 100644 index ac75c60..0000000 --- a/packages/bugsnag_flutter_performance/android/src/main/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePlugin.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.bugsnag_flutter_performance - -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result - -/** BugsnagFlutterPerformancePlugin */ -class BugsnagFlutterPerformancePlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "bugsnag_flutter_performance") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } -} diff --git a/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt b/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt deleted file mode 100644 index 716d866..0000000 --- a/packages/bugsnag_flutter_performance/android/src/test/kotlin/com/example/bugsnag_flutter_performance/BugsnagFlutterPerformancePluginTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.bugsnag_flutter_performance - -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import kotlin.test.Test -import org.mockito.Mockito - -/* - * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. - * - * Once you have built the plugin's example app, you can run these tests from the command - * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or - * you can run them directly from IDEs that support JUnit such as Android Studio. - */ - -internal class BugsnagFlutterPerformancePluginTest { - @Test - fun onMethodCall_getPlatformVersion_returnsExpectedValue() { - val plugin = BugsnagFlutterPerformancePlugin() - - val call = MethodCall("getPlatformVersion", null) - val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) - plugin.onMethodCall(call, mockResult) - - Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) - } -} diff --git a/packages/bugsnag_flutter_performance/ios/.gitignore b/packages/bugsnag_flutter_performance/ios/.gitignore deleted file mode 100644 index 0c88507..0000000 --- a/packages/bugsnag_flutter_performance/ios/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig -/Flutter/ephemeral/ -/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/bugsnag_flutter_performance/ios/Assets/.gitkeep b/packages/bugsnag_flutter_performance/ios/Assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift b/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift deleted file mode 100644 index 6551949..0000000 --- a/packages/bugsnag_flutter_performance/ios/Classes/BugsnagFlutterPerformancePlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Flutter -import UIKit - -public class BugsnagFlutterPerformancePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "bugsnag_flutter_performance", binaryMessenger: registrar.messenger()) - let instance = BugsnagFlutterPerformancePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("iOS " + UIDevice.current.systemVersion) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy b/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index c03d390..0000000 --- a/packages/bugsnag_flutter_performance/ios/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,45 +0,0 @@ - - - - - NSPrivacyCollectedDataTypes - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeDeviceID - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAppFunctionality - - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeProductInteraction - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAppFunctionality - - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeOtherDataTypes - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAppFunctionality - - - - - diff --git a/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec b/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec deleted file mode 100644 index afe523a..0000000 --- a/packages/bugsnag_flutter_performance/ios/bugsnag_flutter_performance.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint bugsnag_flutter_performance.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'bugsnag_flutter_performance' - s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' - s.description = <<-DESC -A new Flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.platform = :ios, '11.0' - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' -end diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart deleted file mode 100644 index 3064daf..0000000 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_method_channel.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'bugsnag_flutter_performance_platform_interface.dart'; - -/// An implementation of [BugsnagFlutterPerformancePlatform] that uses method channels. -class MethodChannelBugsnagFlutterPerformance - extends BugsnagFlutterPerformancePlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('bugsnag_flutter_performance'); - - @override - Future getPlatformVersion() async { - final version = - await methodChannel.invokeMethod('getPlatformVersion'); - return version; - } -} diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart deleted file mode 100644 index a404bfc..0000000 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance_platform_interface.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'bugsnag_flutter_performance_method_channel.dart'; - -abstract class BugsnagFlutterPerformancePlatform extends PlatformInterface { - /// Constructs a BugsnagFlutterPerformancePlatform. - BugsnagFlutterPerformancePlatform() : super(token: _token); - - static final Object _token = Object(); - - static BugsnagFlutterPerformancePlatform _instance = - MethodChannelBugsnagFlutterPerformance(); - - /// The default instance of [BugsnagFlutterPerformancePlatform] to use. - /// - /// Defaults to [MethodChannelBugsnagFlutterPerformance]. - static BugsnagFlutterPerformancePlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [BugsnagFlutterPerformancePlatform] when - /// they register themselves. - static set instance(BugsnagFlutterPerformancePlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 7572f0a..20c17eb 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -39,10 +39,4 @@ flutter: # This is required for using `dart:ffi`. # All these are used by the tooling to maintain consistency when # adding or updating assets for this project. - plugin: - platforms: - android: - package: com.example.bugsnag_flutter_performance - pluginClass: BugsnagFlutterPerformancePlugin - ios: - pluginClass: BugsnagFlutterPerformancePlugin + From a9c451b9171e1b28c34ed987b9c7ac7dfd0c1062 Mon Sep 17 00:00:00 2001 From: robert-smartbear <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:52:44 +0200 Subject: [PATCH 63/66] Made startNavigationSpan routeName a positional argument (#58) --- .../lib/scenarios/manual_navigation_span_scenario.dart | 2 +- .../lib/bugsnag_flutter_performance.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart b/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart index ff20117..e693804 100644 --- a/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart +++ b/features/fixture_resources/lib/scenarios/manual_navigation_span_scenario.dart @@ -8,7 +8,7 @@ class ManualNavigationSpanScenario extends Scenario { setMaxBatchSize(1); bugsnag_performance .startNavigationSpan( - routeName: 'navigationScenarioRoute', + 'navigationScenarioRoute', navigatorName: 'customNavigator', previousRoute: 'navigationScenarioPreviousRoute', ) diff --git a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart index 7d65b54..8e073b6 100644 --- a/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart +++ b/packages/bugsnag_flutter_performance/lib/bugsnag_flutter_performance.dart @@ -81,8 +81,8 @@ class BugsnagPerformance { return _client.startNetworkSpan(url, httpMethod.toUpperCase()); } - BugsnagPerformanceSpan startNavigationSpan({ - required String routeName, + BugsnagPerformanceSpan startNavigationSpan( + String routeName, { String? navigatorName, String? previousRoute, BugsnagPerformanceSpanContext? parentContext, From caf8a882c8a18ab5932415f6ec929a24d8a8a93b Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Thu, 11 Apr 2024 13:34:39 +0100 Subject: [PATCH 64/66] chore(build): update package gemfile --- Gemfile.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index db9a2b9..8d74e3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -154,7 +154,6 @@ GEM mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.1003) - mini_portile2 (2.8.5) minitest (5.22.3) molinillo (0.8.0) multi_test (0.1.2) @@ -163,15 +162,16 @@ GEM nap (1.1.0) netrc (0.11.0) nkf (0.2.0) - nokogiri (1.15.5) - mini_portile2 (~> 2.8.2) + nokogiri (1.15.6-arm64-darwin) + racc (~> 1.4) + nokogiri (1.15.6-x86_64-darwin) racc (~> 1.4) optimist (3.0.1) os (1.0.1) power_assert (2.0.3) public_suffix (4.0.7) racc (1.7.3) - rack (2.2.8) + rack (2.2.9) rake (12.3.3) regexp_parser (2.8.2) rexml (3.2.6) @@ -211,6 +211,7 @@ GEM rexml (~> 3.2.4) PLATFORMS + arm64-darwin-22 x86_64-darwin-20 x86_64-darwin-21 x86_64-darwin-23 @@ -220,4 +221,4 @@ DEPENDENCIES cocoapods BUNDLED WITH - 2.4.8 + 2.4.18 From 7aa72d094ac194a82ad555f0789117a8102ab660 Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 11 Apr 2024 15:37:05 +0200 Subject: [PATCH 65/66] initial release prep (#61) --- CONTRIBUTING.md | 21 +++++++++ Makefile | 43 ++++--------------- .../bugsnag_flutter_performance/pubspec.yaml | 4 +- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 810ce60..72b3065 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,29 @@ Here’s a bit about our process designing and building the Bugsnag libraries: environments. Oftentimes, this requires an intensive engineering design and code review process that adheres to our style and linting guidelines. +### Making a Release + +- Using GitHub create a new releasing branch from `next`: `releases/v` +- Checkout the release branch + - Bump the version number: `make VERSION= bump` + - Inspect the updated CHANGELOG, README, and version files to ensure they are correct + - Open a PR from the release branch to `main` +- Once merged: + - Pull the latest changes from `main` + - Run `git clean -df` to ensure no unexpected files make it into the release + - Creating the staged release: `make stage` + - Publish the new version to pub.dev: + - `cd staging/bugsnag_flutter_performance && flutter pub publish` +- Release on GitHub: + - Create a release and tag from `main` + on [GitHub Releases](https://github.com/bugsnag/bugsnag-flutter-performance/releases) +- Merge outstanding docs PRs related to this release +- Merge main into next + ## Further development docs For information on how to build the library and develop changes you should start by reading [the docs](docs/RELEASING.md). + + diff --git a/Makefile b/Makefile index fba627b..42c1812 100644 --- a/Makefile +++ b/Makefile @@ -22,40 +22,15 @@ endif sed -i '' "s/## TBD/## $(VERSION) ($(shell date '+%Y-%m-%d'))/" CHANGELOG.md sed -i '' "s/^version: .*/version: $(VERSION)/" packages/bugsnag_flutter_performance/pubspec.yaml -prerelease: bump -ifeq ($(VERSION),) - @$(error VERSION is not defined. Run with `make VERSION=number prerelease`) -endif - @git checkout -b release-v$(VERSION) - @git add CHANGELOG.md packages/bugsnag_flutter_performance/pubspec.yaml - @git diff --exit-code || (echo "you have unstaged changes - Makefile may need updating to `git add` some more files"; exit 1) - @git commit -m "Release v$(VERSION)" - @git push origin release-v$(VERSION) - @open "https://github.com/bugsnag/bugsnag-flutter-performance/compare/main...release-v$(VERSION)?expand=1&title=Release%20v$(VERSION)&body="$$(awk 'start && /^## /{exit;};/^## /{start=1;next};start' CHANGELOG.md | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g') - -release: ## Releases the current main branch as $VERSION - @git fetch origin -ifneq ($(shell git rev-parse --abbrev-ref HEAD),main) # Check the current branch name - @git checkout main - @git rebase origin/main -endif -ifneq ($(shell git diff origin/main..main),) - $(error you have unpushed commits on the main branch) -endif - @git tag v$(PRESET_VERSION) - # Swift Package Manager prefers tags to be unprefixed package versions - @git tag $(PRESET_VERSION) - @git push origin v$(PRESET_VERSION) $(PRESET_VERSION) - @git checkout next - @git rebase origin/next - @git merge main - @git push origin next - # Prep GitHub release - # We could technically do a `hub release` here but a verification step - # before it goes live always seems like a good thing - @open 'https://github.com/bugsnag/bugsnag-flutter-performance/releases/new?title=v$(PRESET_VERSION)&tag=v$(PRESET_VERSION)&body='$$(awk 'start && /^## /{exit;};/^## /{start=1;next};start' CHANGELOG.md | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g') - @git clean -df - cd packages/bugsnag_flutter_performance && flutter pub publish +staging: staging + mkdir -p staging/bugsnag_flutter_performance + cd packages/bugsnag_flutter_performance && cp -a . ../../staging/bugsnag_flutter_performance + rm -f staging/bugsnag_flutter_performance/pubspec.lock + cp -r example staging/bugsnag_flutter_performance/example + cp README.md staging/bugsnag_flutter_performance/. + cp LICENSE staging/bugsnag_flutter_performance/. + cp CHANGELOG.md staging/bugsnag_flutter_performance/. + sed -i '' -e '1,2d' staging/bugsnag_flutter_performance/CHANGELOG.md aar: cd packages/bugsnag_flutter_performance && $(FLUTTER_BIN) build aar --suppress-analytics diff --git a/packages/bugsnag_flutter_performance/pubspec.yaml b/packages/bugsnag_flutter_performance/pubspec.yaml index 20c17eb..076cf6a 100644 --- a/packages/bugsnag_flutter_performance/pubspec.yaml +++ b/packages/bugsnag_flutter_performance/pubspec.yaml @@ -1,12 +1,10 @@ name: bugsnag_flutter_performance description: BugSnag performance monitoring tool for Flutter apps -version: 0.0.1 +version: 1.0.0 homepage: https://www.bugsnag.com/ documentation: https://docs.bugsnag.com/performance/flutter/ repository: https://github.com/bugsnag/bugsnag-flutter issue_tracker: https://github.com/bugsnag/bugsnag-flutter/issues -# TODO: Remove once all dependencies are published -publish_to: none environment: sdk: '>=3.0.0 <4.0.0' From 00eabdb1ed711cbe7bb9a2ca1e661d8745b1637b Mon Sep 17 00:00:00 2001 From: Richard Elms Date: Thu, 11 Apr 2024 16:18:30 +0200 Subject: [PATCH 66/66] Release v1.0.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edadeb1..6302d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ Changelog ========= -## 1.0.0 (TBD) +## 1.0.0 (2024-04-11) Initial release
+ + + + SmartBear BugSnag logo + + +

Performance monitoring for Flutter

+