Add `--trace-to-file` option to `flutter run` (#135713)

diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index dd9e3d7..dc9bf5e 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -625,6 +625,7 @@
     final String dartVmFlags = computeDartVmFlags(debuggingOptions);
     final String? traceAllowlist = debuggingOptions.traceAllowlist;
     final String? traceSkiaAllowlist = debuggingOptions.traceSkiaAllowlist;
+    final String? traceToFile = debuggingOptions.traceToFile;
     final List<String> cmd = <String>[
       'shell', 'am', 'start',
       '-a', 'android.intent.action.MAIN',
@@ -648,6 +649,8 @@
         ...<String>['--es', 'trace-skia-allowlist', traceSkiaAllowlist],
       if (debuggingOptions.traceSystrace)
         ...<String>['--ez', 'trace-systrace', 'true'],
+      if (traceToFile != null)
+        ...<String>['--es', 'trace-to-file', traceToFile],
       if (debuggingOptions.endlessTraceBuffer)
         ...<String>['--ez', 'endless-trace-buffer', 'true'],
       if (debuggingOptions.dumpSkpOnShaderCompilation)
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index bb6538f..3557095 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -119,6 +119,12 @@
               'platforms where such a tracer is available (Android, iOS, '
               'macOS and Fuchsia).',
       )
+      ..addOption('trace-to-file',
+        help: 'Write the timeline trace to a file at the specified path. The '
+              "file will be in Perfetto's proto format; it will be possible to "
+              "load the file into Perfetto's trace viewer.",
+        valueHelp: 'path/to/trace.binpb',
+      )
       ..addFlag('trace-skia',
         negatable: false,
         help: 'Enable tracing of Skia code. This is useful when debugging '
@@ -270,6 +276,7 @@
         traceAllowlist: traceAllowlist,
         traceSkiaAllowlist: stringArg('trace-skia-allowlist'),
         traceSystrace: boolArg('trace-systrace'),
+        traceToFile: stringArg('trace-to-file'),
         endlessTraceBuffer: boolArg('endless-trace-buffer'),
         dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation,
         cacheSkSL: cacheSkSL,
diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart
index f1bfead..817c3d4 100644
--- a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart
+++ b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart
@@ -300,6 +300,8 @@
         'trace-allowlist=${debuggingOptions.traceAllowlist}',
       if (debuggingOptions.traceSystrace)
         'trace-systrace=true',
+      if (debuggingOptions.traceToFile != null)
+        'trace-to-file=${debuggingOptions.traceToFile}',
       if (debuggingOptions.endlessTraceBuffer)
         'endless-trace-buffer=true',
       if (debuggingOptions.dumpSkpOnShaderCompilation)
diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart
index 8327177..6931a17 100644
--- a/packages/flutter_tools/lib/src/desktop_device.dart
+++ b/packages/flutter_tools/lib/src/desktop_device.dart
@@ -254,6 +254,9 @@
     if (debuggingOptions.traceSystrace) {
       addFlag('trace-systrace=true');
     }
+    if (debuggingOptions.traceToFile != null) {
+      addFlag('trace-to-file=${debuggingOptions.traceToFile}');
+    }
     if (debuggingOptions.endlessTraceBuffer) {
       addFlag('endless-trace-buffer=true');
     }
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index aecf35a..869751e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -937,6 +937,7 @@
     this.traceAllowlist,
     this.traceSkiaAllowlist,
     this.traceSystrace = false,
+    this.traceToFile,
     this.endlessTraceBuffer = false,
     this.dumpSkpOnShaderCompilation = false,
     this.cacheSkSL = false,
@@ -1006,6 +1007,7 @@
       traceSkia = false,
       traceSkiaAllowlist = null,
       traceSystrace = false,
+      traceToFile = null,
       endlessTraceBuffer = false,
       dumpSkpOnShaderCompilation = false,
       purgePersistentCache = false,
@@ -1037,6 +1039,7 @@
     required this.traceAllowlist,
     required this.traceSkiaAllowlist,
     required this.traceSystrace,
+    required this.traceToFile,
     required this.endlessTraceBuffer,
     required this.dumpSkpOnShaderCompilation,
     required this.cacheSkSL,
@@ -1088,6 +1091,7 @@
   final String? traceAllowlist;
   final String? traceSkiaAllowlist;
   final bool traceSystrace;
+  final String? traceToFile;
   final bool endlessTraceBuffer;
   final bool dumpSkpOnShaderCompilation;
   final bool cacheSkSL;
@@ -1178,6 +1182,7 @@
       ],
       if (enableSoftwareRendering) '--enable-software-rendering',
       if (traceSystrace) '--trace-systrace',
+      if (traceToFile != null) '--trace-to-file="$traceToFile"',
       if (skiaDeterministicRendering) '--skia-deterministic-rendering',
       if (traceSkia) '--trace-skia',
       if (traceAllowlist != null) '--trace-allowlist="$traceAllowlist"',
@@ -1218,6 +1223,7 @@
     'traceAllowlist': traceAllowlist,
     'traceSkiaAllowlist': traceSkiaAllowlist,
     'traceSystrace': traceSystrace,
+    'traceToFile': traceToFile,
     'endlessTraceBuffer': endlessTraceBuffer,
     'dumpSkpOnShaderCompilation': dumpSkpOnShaderCompilation,
     'cacheSkSL': cacheSkSL,
@@ -1269,6 +1275,7 @@
       traceAllowlist: json['traceAllowlist'] as String?,
       traceSkiaAllowlist: json['traceSkiaAllowlist'] as String?,
       traceSystrace: json['traceSystrace']! as bool,
+      traceToFile: json['traceToFile'] as String?,
       endlessTraceBuffer: json['endlessTraceBuffer']! as bool,
       dumpSkpOnShaderCompilation: json['dumpSkpOnShaderCompilation']! as bool,
       cacheSkSL: json['cacheSkSL']! as bool,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index f3df962..ec7194f 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -417,6 +417,7 @@
       '--disable-service-auth-codes',
       '--trace-skia',
       '--trace-systrace',
+      '--trace-to-file=path/to/trace.binpb',
       '--verbose-system-logs',
       '--null-assertions',
       '--native-null-assertions',
@@ -434,6 +435,7 @@
     expect(options.disableServiceAuthCodes, true);
     expect(options.traceSkia, true);
     expect(options.traceSystrace, true);
+    expect(options.traceToFile, 'path/to/trace.binpb');
     expect(options.verboseSystemLogs, true);
     expect(options.nullAssertions, true);
     expect(options.nativeNullAssertions, true);
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index 2a5a644..81ec6c1 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -1086,6 +1086,7 @@
       '--use-test-fonts',
       '--trace-skia',
       '--trace-systrace',
+      '--trace-to-file=path/to/trace.binpb',
       '--verbose-system-logs',
       '--null-assertions',
       '--native-null-assertions',
@@ -1106,6 +1107,7 @@
     expect(options.useTestFonts, true);
     expect(options.traceSkia, true);
     expect(options.traceSystrace, true);
+    expect(options.traceToFile, 'path/to/trace.binpb');
     expect(options.verboseSystemLogs, true);
     expect(options.nullAssertions, true);
     expect(options.nativeNullAssertions, true);
diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart
index e426cd3..186f507 100644
--- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart
@@ -240,6 +240,7 @@
         '--es', 'trace-allowlist', 'bar,baz',
         '--es', 'trace-skia-allowlist', 'skia.a,skia.b',
         '--ez', 'trace-systrace', 'true',
+        '--es', 'trace-to-file', 'path/to/trace.binpb',
         '--ez', 'endless-trace-buffer', 'true',
         '--ez', 'dump-skp-on-shader-compilation', 'true',
         '--ez', 'cache-sksl', 'true',
@@ -271,6 +272,7 @@
         traceAllowlist: 'bar,baz',
         traceSkiaAllowlist: 'skia.a,skia.b',
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         endlessTraceBuffer: true,
         dumpSkpOnShaderCompilation: true,
         cacheSkSL: true,
diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart
index 3989047..b42194d 100644
--- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart
@@ -151,19 +151,20 @@
           'FLUTTER_ENGINE_SWITCH_6': 'trace-allowlist=foo,bar',
           'FLUTTER_ENGINE_SWITCH_7': 'trace-skia-allowlist=skia.a,skia.b',
           'FLUTTER_ENGINE_SWITCH_8': 'trace-systrace=true',
-          'FLUTTER_ENGINE_SWITCH_9': 'endless-trace-buffer=true',
-          'FLUTTER_ENGINE_SWITCH_10': 'dump-skp-on-shader-compilation=true',
-          'FLUTTER_ENGINE_SWITCH_11': 'cache-sksl=true',
-          'FLUTTER_ENGINE_SWITCH_12': 'purge-persistent-cache=true',
-          'FLUTTER_ENGINE_SWITCH_13': 'enable-impeller=false',
-          'FLUTTER_ENGINE_SWITCH_14': 'enable-checked-mode=true',
-          'FLUTTER_ENGINE_SWITCH_15': 'verify-entry-points=true',
-          'FLUTTER_ENGINE_SWITCH_16': 'start-paused=true',
-          'FLUTTER_ENGINE_SWITCH_17': 'disable-service-auth-codes=true',
-          'FLUTTER_ENGINE_SWITCH_18': 'dart-flags=--null_assertions',
-          'FLUTTER_ENGINE_SWITCH_19': 'use-test-fonts=true',
-          'FLUTTER_ENGINE_SWITCH_20': 'verbose-logging=true',
-          'FLUTTER_ENGINE_SWITCHES': '20',
+          'FLUTTER_ENGINE_SWITCH_9': 'trace-to-file=path/to/trace.binpb',
+          'FLUTTER_ENGINE_SWITCH_10': 'endless-trace-buffer=true',
+          'FLUTTER_ENGINE_SWITCH_11': 'dump-skp-on-shader-compilation=true',
+          'FLUTTER_ENGINE_SWITCH_12': 'cache-sksl=true',
+          'FLUTTER_ENGINE_SWITCH_13': 'purge-persistent-cache=true',
+          'FLUTTER_ENGINE_SWITCH_14': 'enable-impeller=false',
+          'FLUTTER_ENGINE_SWITCH_15': 'enable-checked-mode=true',
+          'FLUTTER_ENGINE_SWITCH_16': 'verify-entry-points=true',
+          'FLUTTER_ENGINE_SWITCH_17': 'start-paused=true',
+          'FLUTTER_ENGINE_SWITCH_18': 'disable-service-auth-codes=true',
+          'FLUTTER_ENGINE_SWITCH_19': 'dart-flags=--null_assertions',
+          'FLUTTER_ENGINE_SWITCH_20': 'use-test-fonts=true',
+          'FLUTTER_ENGINE_SWITCH_21': 'verbose-logging=true',
+          'FLUTTER_ENGINE_SWITCHES': '21',
         }
       ),
     ]);
@@ -185,6 +186,7 @@
         traceAllowlist: 'foo,bar',
         traceSkiaAllowlist: 'skia.a,skia.b',
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         endlessTraceBuffer: true,
         dumpSkpOnShaderCompilation: true,
         cacheSkSL: true,
diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart
index 14e3dbb..74af292 100644
--- a/packages/flutter_tools/test/general.shard/device_test.dart
+++ b/packages/flutter_tools/test/general.shard/device_test.dart
@@ -814,6 +814,7 @@
         traceAllowlist: 'foo',
         traceSkiaAllowlist: 'skia.a,skia.b',
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         endlessTraceBuffer: true,
         dumpSkpOnShaderCompilation: true,
         cacheSkSL: true,
@@ -846,6 +847,7 @@
           '--verify-entry-points',
           '--enable-software-rendering',
           '--trace-systrace',
+          '--trace-to-file="path/to/trace.binpb"',
           '--skia-deterministic-rendering',
           '--trace-skia',
           '--trace-allowlist="foo"',
@@ -994,6 +996,7 @@
         traceAllowlist: 'foo',
         traceSkiaAllowlist: 'skia.a,skia.b',
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         endlessTraceBuffer: true,
         dumpSkpOnShaderCompilation: true,
         cacheSkSL: true,
@@ -1026,6 +1029,7 @@
           '--verify-entry-points',
           '--enable-software-rendering',
           '--trace-systrace',
+          '--trace-to-file="path/to/trace.binpb"',
           '--skia-deterministic-rendering',
           '--trace-skia',
           '--trace-allowlist="foo"',
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
index 90f8340..117888a 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
@@ -485,6 +485,7 @@
             '--verify-entry-points',
             '--enable-software-rendering',
             '--trace-systrace',
+            '--trace-to-file="path/to/trace.binpb"',
             '--skia-deterministic-rendering',
             '--trace-skia',
             '--trace-allowlist="foo"',
@@ -541,6 +542,7 @@
         traceAllowlist: 'foo',
         traceSkiaAllowlist: 'skia.a,skia.b',
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         endlessTraceBuffer: true,
         dumpSkpOnShaderCompilation: true,
         cacheSkSL: true,
diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
index f3830d0..3748510 100644
--- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
@@ -1184,6 +1184,7 @@
         mockInfo,
         enableSoftwareRendering: true,
         traceSystrace: true,
+        traceToFile: 'path/to/trace.binpb',
         startPaused: true,
         disableServiceAuthCodes: true,
         skiaDeterministicRendering: true,
@@ -1209,6 +1210,7 @@
         '--verify-entry-points',
         '--enable-software-rendering',
         '--trace-systrace',
+        '--trace-to-file="path/to/trace.binpb"',
         '--start-paused',
         '--disable-service-auth-codes',
         '--skia-deterministic-rendering',