Unverified Commit 89a3c353 authored by Lau Ching Jun's avatar Lau Ching Jun Committed by GitHub

Add more analytics for hot reload in flutter_tools. (#83972)

parent 1e328e2b
...@@ -19,7 +19,10 @@ class StopwatchFactory { ...@@ -19,7 +19,10 @@ class StopwatchFactory {
const StopwatchFactory(); const StopwatchFactory();
/// Create a new [Stopwatch] instance. /// Create a new [Stopwatch] instance.
Stopwatch createStopwatch() => Stopwatch(); ///
/// The optional [name] parameter is useful in tests when there are multiple
/// instances being created.
Stopwatch createStopwatch([String name = '']) => Stopwatch();
} }
typedef VoidCallback = void Function(); typedef VoidCallback = void Function();
......
...@@ -399,18 +399,34 @@ class UpdateFSReport { ...@@ -399,18 +399,34 @@ class UpdateFSReport {
int invalidatedSourcesCount = 0, int invalidatedSourcesCount = 0,
int syncedBytes = 0, int syncedBytes = 0,
this.fastReassembleClassName, this.fastReassembleClassName,
int scannedSourcesCount = 0,
Duration compileDuration = Duration.zero,
Duration transferDuration = Duration.zero,
Duration findInvalidatedDuration = Duration.zero,
}) : _success = success, }) : _success = success,
_invalidatedSourcesCount = invalidatedSourcesCount, _invalidatedSourcesCount = invalidatedSourcesCount,
_syncedBytes = syncedBytes; _syncedBytes = syncedBytes,
_scannedSourcesCount = scannedSourcesCount,
_compileDuration = compileDuration,
_transferDuration = transferDuration,
_findInvalidatedDuration = findInvalidatedDuration;
bool get success => _success; bool get success => _success;
int get invalidatedSourcesCount => _invalidatedSourcesCount; int get invalidatedSourcesCount => _invalidatedSourcesCount;
int get syncedBytes => _syncedBytes; int get syncedBytes => _syncedBytes;
int get scannedSourcesCount => _scannedSourcesCount;
Duration get compileDuration => _compileDuration;
Duration get transferDuration => _transferDuration;
Duration get findInvalidatedDuration => _findInvalidatedDuration;
bool _success; bool _success;
String fastReassembleClassName; String fastReassembleClassName;
int _invalidatedSourcesCount; int _invalidatedSourcesCount;
int _syncedBytes; int _syncedBytes;
int _scannedSourcesCount;
Duration _compileDuration;
Duration _transferDuration;
Duration _findInvalidatedDuration;
void incorporateResults(UpdateFSReport report) { void incorporateResults(UpdateFSReport report) {
if (!report._success) { if (!report._success) {
...@@ -419,6 +435,10 @@ class UpdateFSReport { ...@@ -419,6 +435,10 @@ class UpdateFSReport {
fastReassembleClassName ??= report.fastReassembleClassName; fastReassembleClassName ??= report.fastReassembleClassName;
_invalidatedSourcesCount += report._invalidatedSourcesCount; _invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes; _syncedBytes += report._syncedBytes;
_scannedSourcesCount += report._scannedSourcesCount;
_compileDuration += report._compileDuration;
_transferDuration += report._transferDuration;
_findInvalidatedDuration += report._findInvalidatedDuration;
} }
} }
...@@ -435,6 +455,7 @@ class DevFS { ...@@ -435,6 +455,7 @@ class DevFS {
@required FileSystem fileSystem, @required FileSystem fileSystem,
HttpClient httpClient, HttpClient httpClient,
Duration uploadRetryThrottle, Duration uploadRetryThrottle,
StopwatchFactory stopwatchFactory = const StopwatchFactory(),
}) : _vmService = serviceProtocol, }) : _vmService = serviceProtocol,
_logger = logger, _logger = logger,
_fileSystem = fileSystem, _fileSystem = fileSystem,
...@@ -446,13 +467,14 @@ class DevFS { ...@@ -446,13 +467,14 @@ class DevFS {
uploadRetryThrottle: uploadRetryThrottle, uploadRetryThrottle: uploadRetryThrottle,
httpClient: httpClient ?? ((context.get<HttpClientFactory>() == null) httpClient: httpClient ?? ((context.get<HttpClientFactory>() == null)
? HttpClient() ? HttpClient()
: context.get<HttpClientFactory>()()) : context.get<HttpClientFactory>()())),
); _stopwatchFactory = stopwatchFactory;
final FlutterVmService _vmService; final FlutterVmService _vmService;
final _DevFSHttpWriter _httpWriter; final _DevFSHttpWriter _httpWriter;
final Logger _logger; final Logger _logger;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final StopwatchFactory _stopwatchFactory;
final String fsName; final String fsName;
final Directory rootDirectory; final Directory rootDirectory;
...@@ -575,6 +597,7 @@ class DevFS { ...@@ -575,6 +597,7 @@ class DevFS {
// Await the compiler response after checking if the bundle is updated. This allows the file // Await the compiler response after checking if the bundle is updated. This allows the file
// stating to be done while waiting for the frontend_server response. // stating to be done while waiting for the frontend_server response.
final Stopwatch compileTimer = _stopwatchFactory.createStopwatch('compile')..start();
final Future<CompilerOutput> pendingCompilerOutput = generator.recompile( final Future<CompilerOutput> pendingCompilerOutput = generator.recompile(
mainUri, mainUri,
invalidatedFiles, invalidatedFiles,
...@@ -582,7 +605,11 @@ class DevFS { ...@@ -582,7 +605,11 @@ class DevFS {
fs: _fileSystem, fs: _fileSystem,
projectRootPath: projectRootPath, projectRootPath: projectRootPath,
packageConfig: packageConfig, packageConfig: packageConfig,
); ).then((CompilerOutput result) {
compileTimer.stop();
return result;
});
if (bundle != null) { if (bundle != null) {
// The tool writes the assets into the AssetBundle working dir so that they // The tool writes the assets into the AssetBundle working dir so that they
// are in the same location in DevFS and the iOS simulator. // are in the same location in DevFS and the iOS simulator.
...@@ -627,15 +654,19 @@ class DevFS { ...@@ -627,15 +654,19 @@ class DevFS {
} }
} }
_logger.printTrace('Updating files.'); _logger.printTrace('Updating files.');
final Stopwatch transferTimer = _stopwatchFactory.createStopwatch('transfer')..start();
if (dirtyEntries.isNotEmpty) { if (dirtyEntries.isNotEmpty) {
await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri, _httpWriter); await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri, _httpWriter);
} }
transferTimer.stop();
_logger.printTrace('DevFS: Sync finished'); _logger.printTrace('DevFS: Sync finished');
return UpdateFSReport( return UpdateFSReport(
success: true, success: true,
syncedBytes: syncedBytes, syncedBytes: syncedBytes,
invalidatedSourcesCount: invalidatedFiles.length, invalidatedSourcesCount: invalidatedFiles.length,
fastReassembleClassName: _checkIfSingleWidgetReloadApplied(), fastReassembleClassName: _checkIfSingleWidgetReloadApplied(),
compileDuration: compileTimer.elapsed,
transferDuration: transferTimer.elapsed,
); );
} }
......
...@@ -61,6 +61,11 @@ class CustomDimensions { ...@@ -61,6 +61,11 @@ class CustomDimensions {
this.fastReassemble, this.fastReassemble,
this.nullSafeMigratedLibraries, this.nullSafeMigratedLibraries,
this.nullSafeTotalLibraries, this.nullSafeTotalLibraries,
this.hotEventCompileTimeInMs,
this.hotEventFindInvalidatedTimeInMs,
this.hotEventScannedSourcesCount,
this.hotEventReassembleTimeInMs,
this.hotEventReloadVMTimeInMs,
}); });
final String? sessionHostOsDetails; // cd1 final String? sessionHostOsDetails; // cd1
...@@ -113,6 +118,11 @@ class CustomDimensions { ...@@ -113,6 +118,11 @@ class CustomDimensions {
final bool? fastReassemble; // cd48 final bool? fastReassemble; // cd48
final int? nullSafeMigratedLibraries; // cd49 final int? nullSafeMigratedLibraries; // cd49
final int? nullSafeTotalLibraries; // cd50 final int? nullSafeTotalLibraries; // cd50
final int? hotEventCompileTimeInMs; // cd 51
final int? hotEventFindInvalidatedTimeInMs; // cd 52
final int? hotEventScannedSourcesCount; // cd 53
final int? hotEventReassembleTimeInMs; // cd 54
final int? hotEventReloadVMTimeInMs; // cd 55
/// Convert to a map that will be used to upload to the analytics backend. /// Convert to a map that will be used to upload to the analytics backend.
Map<String, String> toMap() => <String, String>{ Map<String, String> toMap() => <String, String>{
...@@ -166,6 +176,11 @@ class CustomDimensions { ...@@ -166,6 +176,11 @@ class CustomDimensions {
if (fastReassemble != null) cdKey(CustomDimensionsEnum.fastReassemble): fastReassemble.toString(), if (fastReassemble != null) cdKey(CustomDimensionsEnum.fastReassemble): fastReassemble.toString(),
if (nullSafeMigratedLibraries != null) cdKey(CustomDimensionsEnum.nullSafeMigratedLibraries): nullSafeMigratedLibraries.toString(), if (nullSafeMigratedLibraries != null) cdKey(CustomDimensionsEnum.nullSafeMigratedLibraries): nullSafeMigratedLibraries.toString(),
if (nullSafeTotalLibraries != null) cdKey(CustomDimensionsEnum.nullSafeTotalLibraries): nullSafeTotalLibraries.toString(), if (nullSafeTotalLibraries != null) cdKey(CustomDimensionsEnum.nullSafeTotalLibraries): nullSafeTotalLibraries.toString(),
if (hotEventCompileTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventCompileTimeInMs): hotEventCompileTimeInMs.toString(),
if (hotEventFindInvalidatedTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventFindInvalidatedTimeInMs): hotEventFindInvalidatedTimeInMs.toString(),
if (hotEventScannedSourcesCount != null) cdKey(CustomDimensionsEnum.hotEventScannedSourcesCount): hotEventScannedSourcesCount.toString(),
if (hotEventReassembleTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReassembleTimeInMs): hotEventReassembleTimeInMs.toString(),
if (hotEventReloadVMTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReloadVMTimeInMs): hotEventReloadVMTimeInMs.toString(),
}; };
/// Merge the values of two [CustomDimensions] into one. If a value is defined /// Merge the values of two [CustomDimensions] into one. If a value is defined
...@@ -226,6 +241,11 @@ class CustomDimensions { ...@@ -226,6 +241,11 @@ class CustomDimensions {
fastReassemble: other.fastReassemble ?? fastReassemble, fastReassemble: other.fastReassemble ?? fastReassemble,
nullSafeMigratedLibraries: other.nullSafeMigratedLibraries ?? nullSafeMigratedLibraries, nullSafeMigratedLibraries: other.nullSafeMigratedLibraries ?? nullSafeMigratedLibraries,
nullSafeTotalLibraries: other.nullSafeTotalLibraries ?? nullSafeTotalLibraries, nullSafeTotalLibraries: other.nullSafeTotalLibraries ?? nullSafeTotalLibraries,
hotEventCompileTimeInMs: other.hotEventCompileTimeInMs ?? hotEventCompileTimeInMs,
hotEventFindInvalidatedTimeInMs: other.hotEventFindInvalidatedTimeInMs ?? hotEventFindInvalidatedTimeInMs,
hotEventScannedSourcesCount: other.hotEventScannedSourcesCount ?? hotEventScannedSourcesCount,
hotEventReassembleTimeInMs: other.hotEventReassembleTimeInMs ?? hotEventReassembleTimeInMs,
hotEventReloadVMTimeInMs: other.hotEventReloadVMTimeInMs ?? hotEventReloadVMTimeInMs,
); );
} }
...@@ -280,6 +300,11 @@ class CustomDimensions { ...@@ -280,6 +300,11 @@ class CustomDimensions {
fastReassemble: _extractBool(map, CustomDimensionsEnum.fastReassemble), fastReassemble: _extractBool(map, CustomDimensionsEnum.fastReassemble),
nullSafeMigratedLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeMigratedLibraries), nullSafeMigratedLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeMigratedLibraries),
nullSafeTotalLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeTotalLibraries), nullSafeTotalLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeTotalLibraries),
hotEventCompileTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventCompileTimeInMs),
hotEventFindInvalidatedTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventFindInvalidatedTimeInMs),
hotEventScannedSourcesCount: _extractInt(map, CustomDimensionsEnum.hotEventScannedSourcesCount),
hotEventReassembleTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReassembleTimeInMs),
hotEventReloadVMTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReloadVMTimeInMs),
); );
static bool? _extractBool(Map<String, String> map, CustomDimensionsEnum field) => static bool? _extractBool(Map<String, String> map, CustomDimensionsEnum field) =>
...@@ -365,6 +390,11 @@ enum CustomDimensionsEnum { ...@@ -365,6 +390,11 @@ enum CustomDimensionsEnum {
fastReassemble, // cd48 fastReassemble, // cd48
nullSafeMigratedLibraries, // cd49 nullSafeMigratedLibraries, // cd49
nullSafeTotalLibraries, // cd50 nullSafeTotalLibraries, // cd50
hotEventCompileTimeInMs, // cd51
hotEventFindInvalidatedTimeInMs, // cd52
hotEventScannedSourcesCount, // cd53
hotEventReassembleTimeInMs, // cd54
hotEventReloadVMTimeInMs, // cd55
} }
String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}'; String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}';
...@@ -49,6 +49,11 @@ class HotEvent extends UsageEvent { ...@@ -49,6 +49,11 @@ class HotEvent extends UsageEvent {
this.invalidatedSourcesCount, this.invalidatedSourcesCount,
this.transferTimeInMs, this.transferTimeInMs,
this.overallTimeInMs, this.overallTimeInMs,
this.compileTimeInMs,
this.findInvalidatedTimeInMs,
this.scannedSourcesCount,
this.reassembleTimeInMs,
this.reloadVMTimeInMs,
}) : super('hot', parameter, flutterUsage: globals.flutterUsage); }) : super('hot', parameter, flutterUsage: globals.flutterUsage);
final String? reason; final String? reason;
...@@ -65,6 +70,11 @@ class HotEvent extends UsageEvent { ...@@ -65,6 +70,11 @@ class HotEvent extends UsageEvent {
final int? invalidatedSourcesCount; final int? invalidatedSourcesCount;
final int? transferTimeInMs; final int? transferTimeInMs;
final int? overallTimeInMs; final int? overallTimeInMs;
final int? compileTimeInMs;
final int? findInvalidatedTimeInMs;
final int? scannedSourcesCount;
final int? reassembleTimeInMs;
final int? reloadVMTimeInMs;
@override @override
void send() { void send() {
...@@ -83,6 +93,11 @@ class HotEvent extends UsageEvent { ...@@ -83,6 +93,11 @@ class HotEvent extends UsageEvent {
hotEventTransferTimeInMs: transferTimeInMs, hotEventTransferTimeInMs: transferTimeInMs,
hotEventOverallTimeInMs: overallTimeInMs, hotEventOverallTimeInMs: overallTimeInMs,
fastReassemble: fastReassemble, fastReassemble: fastReassemble,
hotEventCompileTimeInMs: compileTimeInMs,
hotEventFindInvalidatedTimeInMs: findInvalidatedTimeInMs,
hotEventScannedSourcesCount: scannedSourcesCount,
hotEventReassembleTimeInMs: reassembleTimeInMs,
hotEventReloadVMTimeInMs: reloadVMTimeInMs,
); );
flutterUsage.sendEvent(category, parameter, parameters: parameters); flutterUsage.sendEvent(category, parameter, parameters: parameters);
} }
......
...@@ -1460,7 +1460,7 @@ abstract class ResidentRunner extends ResidentHandlers { ...@@ -1460,7 +1460,7 @@ abstract class ResidentRunner extends ResidentHandlers {
} }
class OperationResult { class OperationResult {
OperationResult(this.code, this.message, { this.fatal = false }); OperationResult(this.code, this.message, { this.fatal = false, this.updateFSReport });
/// The result of the operation; a non-zero code indicates a failure. /// The result of the operation; a non-zero code indicates a failure.
final int code; final int code;
...@@ -1471,6 +1471,8 @@ class OperationResult { ...@@ -1471,6 +1471,8 @@ class OperationResult {
/// Whether this error should cause the runner to exit. /// Whether this error should cause the runner to exit.
final bool fatal; final bool fatal;
final UpdateFSReport updateFSReport;
bool get isOk => code == 0; bool get isOk => code == 0;
static final OperationResult ok = OperationResult(0, ''); static final OperationResult ok = OperationResult(0, '');
......
...@@ -82,7 +82,13 @@ class HotRunner extends ResidentRunner { ...@@ -82,7 +82,13 @@ class HotRunner extends ResidentRunner {
bool ipv6 = false, bool ipv6 = false,
bool machine = false, bool machine = false,
ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler, ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
}) : super( StopwatchFactory stopwatchFactory = const StopwatchFactory(),
ReloadSourcesHelper reloadSourcesHelper = _defaultReloadSourcesHelper,
ReassembleHelper reassembleHelper = _defaultReassembleHelper,
}) : _stopwatchFactory = stopwatchFactory,
_reloadSourcesHelper = reloadSourcesHelper,
_reassembleHelper = reassembleHelper,
super(
devices, devices,
target: target, target: target,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
...@@ -95,6 +101,10 @@ class HotRunner extends ResidentRunner { ...@@ -95,6 +101,10 @@ class HotRunner extends ResidentRunner {
devtoolsHandler: devtoolsHandler, devtoolsHandler: devtoolsHandler,
); );
final StopwatchFactory _stopwatchFactory;
final ReloadSourcesHelper _reloadSourcesHelper;
final ReassembleHelper _reassembleHelper;
final bool benchmarkMode; final bool benchmarkMode;
final File applicationBinary; final File applicationBinary;
final bool hostIsIde; final bool hostIsIde;
...@@ -119,6 +129,31 @@ class HotRunner extends ResidentRunner { ...@@ -119,6 +129,31 @@ class HotRunner extends ResidentRunner {
DateTime firstBuildTime; DateTime firstBuildTime;
String _targetPlatform;
String _sdkName;
bool _emulator;
Future<void> _calculateTargetPlatform() async {
if (_targetPlatform != null) {
return;
}
if (flutterDevices.length == 1) {
final Device device = flutterDevices.first.device;
_targetPlatform = getNameForTargetPlatform(await device.targetPlatform);
_sdkName = await device.sdkNameAndVersion;
_emulator = await device.isLocalEmulator;
} else if (flutterDevices.length > 1) {
_targetPlatform = 'multiple';
_sdkName = 'multiple';
_emulator = false;
} else {
_targetPlatform = 'unknown';
_sdkName = 'unknown';
_emulator = false;
}
}
void _addBenchmarkData(String name, int value) { void _addBenchmarkData(String name, int value) {
benchmarkData[name] ??= <int>[]; benchmarkData[name] ??= <int>[];
benchmarkData[name].add(value); benchmarkData[name].add(value);
...@@ -315,9 +350,15 @@ class HotRunner extends ResidentRunner { ...@@ -315,9 +350,15 @@ class HotRunner extends ResidentRunner {
bool enableDevTools = false, bool enableDevTools = false,
String route, String route,
}) async { }) async {
await _calculateTargetPlatform();
final Stopwatch appStartedTimer = Stopwatch()..start();
final File mainFile = globals.fs.file(mainPath); final File mainFile = globals.fs.file(mainPath);
firstBuildTime = DateTime.now(); firstBuildTime = DateTime.now();
Duration totalCompileTime = Duration.zero;
Duration totalLaunchAppTime = Duration.zero;
final List<Future<bool>> startupTasks = <Future<bool>>[]; final List<Future<bool>> startupTasks = <Future<bool>>[];
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
// Here we initialize the frontend_server concurrently with the platform // Here we initialize the frontend_server concurrently with the platform
...@@ -326,6 +367,7 @@ class HotRunner extends ResidentRunner { ...@@ -326,6 +367,7 @@ class HotRunner extends ResidentRunner {
// subsequent invocation in devfs will not overwrite. // subsequent invocation in devfs will not overwrite.
await runSourceGenerators(); await runSourceGenerators();
if (device.generator != null) { if (device.generator != null) {
final Stopwatch compileTimer = Stopwatch()..start();
startupTasks.add( startupTasks.add(
device.generator.recompile( device.generator.recompile(
mainFile.uri, mainFile.uri,
...@@ -342,14 +384,35 @@ class HotRunner extends ResidentRunner { ...@@ -342,14 +384,35 @@ class HotRunner extends ResidentRunner {
packageConfig: debuggingOptions.buildInfo.packageConfig, packageConfig: debuggingOptions.buildInfo.packageConfig,
projectRootPath: FlutterProject.current().directory.absolute.path, projectRootPath: FlutterProject.current().directory.absolute.path,
fs: globals.fs, fs: globals.fs,
).then((CompilerOutput output) => output?.errorCount == 0) ).then((CompilerOutput output) {
compileTimer.stop();
totalCompileTime += compileTimer.elapsed;
return output?.errorCount == 0;
})
); );
} }
final Stopwatch launchAppTimer = Stopwatch()..start();
startupTasks.add(device.runHot( startupTasks.add(device.runHot(
hotRunner: this, hotRunner: this,
route: route, route: route,
).then((int result) => result == 0)); ).then((int result) {
totalLaunchAppTime += launchAppTimer.elapsed;
return result == 0;
}));
} }
unawaited(appStartedCompleter?.future?.then((_) => HotEvent('reload-ready',
targetPlatform: _targetPlatform,
sdkName: _sdkName,
emulator: _emulator,
fullRestart: null,
fastReassemble: null,
overallTimeInMs: appStartedTimer.elapsed.inMilliseconds,
compileTimeInMs: totalCompileTime.inMilliseconds,
transferTimeInMs: totalLaunchAppTime.inMilliseconds,
)?.send()));
try { try {
final List<bool> results = await Future.wait(startupTasks); final List<bool> results = await Future.wait(startupTasks);
if (!results.every((bool passed) => passed)) { if (!results.every((bool passed) => passed)) {
...@@ -392,6 +455,7 @@ class HotRunner extends ResidentRunner { ...@@ -392,6 +455,7 @@ class HotRunner extends ResidentRunner {
} }
} }
final Stopwatch findInvalidationTimer = _stopwatchFactory.createStopwatch('updateDevFS')..start();
final InvalidationResult invalidationResult = await projectFileInvalidator.findInvalidated( final InvalidationResult invalidationResult = await projectFileInvalidator.findInvalidated(
lastCompiled: flutterDevices[0].devFS.lastCompiled, lastCompiled: flutterDevices[0].devFS.lastCompiled,
urisToMonitor: flutterDevices[0].devFS.sources, urisToMonitor: flutterDevices[0].devFS.sources,
...@@ -400,6 +464,7 @@ class HotRunner extends ResidentRunner { ...@@ -400,6 +464,7 @@ class HotRunner extends ResidentRunner {
packageConfig: flutterDevices[0].devFS.lastPackageConfig packageConfig: flutterDevices[0].devFS.lastPackageConfig
?? debuggingOptions.buildInfo.packageConfig, ?? debuggingOptions.buildInfo.packageConfig,
); );
findInvalidationTimer.stop();
final File entrypointFile = globals.fs.file(mainPath); final File entrypointFile = globals.fs.file(mainPath);
if (!entrypointFile.existsSync()) { if (!entrypointFile.existsSync()) {
globals.printError( globals.printError(
...@@ -409,7 +474,11 @@ class HotRunner extends ResidentRunner { ...@@ -409,7 +474,11 @@ class HotRunner extends ResidentRunner {
'flutter is restarted or the file is restored.' 'flutter is restarted or the file is restored.'
); );
} }
final UpdateFSReport results = UpdateFSReport(success: true); final UpdateFSReport results = UpdateFSReport(
success: true,
scannedSourcesCount: flutterDevices[0].devFS.sources.length,
findInvalidatedDuration: findInvalidationTimer.elapsed,
);
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
results.incorporateResults(await device.updateDevFS( results.incorporateResults(await device.updateDevFS(
mainUri: entrypointFile.absolute.uri, mainUri: entrypointFile.absolute.uri,
...@@ -429,12 +498,6 @@ class HotRunner extends ResidentRunner { ...@@ -429,12 +498,6 @@ class HotRunner extends ResidentRunner {
return results; return results;
} }
void _resetDevFSCompileTime() {
for (final FlutterDevice device in flutterDevices) {
device.devFS.resetLastCompiled();
}
}
void _resetDirtyAssets() { void _resetDirtyAssets() {
for (final FlutterDevice device in flutterDevices) { for (final FlutterDevice device in flutterDevices) {
device.devFS.assetPathsToEvict.clear(); device.devFS.assetPathsToEvict.clear();
...@@ -574,7 +637,11 @@ class HotRunner extends ResidentRunner { ...@@ -574,7 +637,11 @@ class HotRunner extends ResidentRunner {
// Toggle the main dill name after successfully uploading. // Toggle the main dill name after successfully uploading.
_swap =! _swap; _swap =! _swap;
return OperationResult.ok; return OperationResult(
OperationResult.ok.code,
OperationResult.ok.message,
updateFSReport: updatedDevFS,
);
} }
/// Returns [true] if the reload was successful. /// Returns [true] if the reload was successful.
...@@ -612,23 +679,7 @@ class HotRunner extends ResidentRunner { ...@@ -612,23 +679,7 @@ class HotRunner extends ResidentRunner {
if (flutterDevices.any((FlutterDevice device) => device.devFS == null)) { if (flutterDevices.any((FlutterDevice device) => device.devFS == null)) {
return OperationResult(1, 'Device initialization has not completed.'); return OperationResult(1, 'Device initialization has not completed.');
} }
String targetPlatform; await _calculateTargetPlatform();
String sdkName;
bool emulator;
if (flutterDevices.length == 1) {
final Device device = flutterDevices.first.device;
targetPlatform = getNameForTargetPlatform(await device.targetPlatform);
sdkName = await device.sdkNameAndVersion;
emulator = await device.isLocalEmulator;
} else if (flutterDevices.length > 1) {
targetPlatform = 'multiple';
sdkName = 'multiple';
emulator = false;
} else {
targetPlatform = 'unknown';
sdkName = 'unknown';
emulator = false;
}
final Stopwatch timer = Stopwatch()..start(); final Stopwatch timer = Stopwatch()..start();
// Run source generation if needed. // Run source generation if needed.
...@@ -636,9 +687,9 @@ class HotRunner extends ResidentRunner { ...@@ -636,9 +687,9 @@ class HotRunner extends ResidentRunner {
if (fullRestart) { if (fullRestart) {
final OperationResult result = await _fullRestartHelper( final OperationResult result = await _fullRestartHelper(
targetPlatform: targetPlatform, targetPlatform: _targetPlatform,
sdkName: sdkName, sdkName: _sdkName,
emulator: emulator, emulator: _emulator,
reason: reason, reason: reason,
silent: silent, silent: silent,
); );
...@@ -649,9 +700,9 @@ class HotRunner extends ResidentRunner { ...@@ -649,9 +700,9 @@ class HotRunner extends ResidentRunner {
return result; return result;
} }
final OperationResult result = await _hotReloadHelper( final OperationResult result = await _hotReloadHelper(
targetPlatform: targetPlatform, targetPlatform: _targetPlatform,
sdkName: sdkName, sdkName: _sdkName,
emulator: emulator, emulator: _emulator,
reason: reason, reason: reason,
pause: pause, pause: pause,
); );
...@@ -682,14 +733,32 @@ class HotRunner extends ResidentRunner { ...@@ -682,14 +733,32 @@ class HotRunner extends ResidentRunner {
); );
} }
OperationResult result; OperationResult result;
String restartEvent = 'restart'; String restartEvent;
try { try {
final Stopwatch restartTimer = _stopwatchFactory.createStopwatch('fullRestartHelper')..start();
if (!(await hotRunnerConfig.setupHotRestart())) { if (!(await hotRunnerConfig.setupHotRestart())) {
return OperationResult(1, 'setupHotRestart failed'); return OperationResult(1, 'setupHotRestart failed');
} }
result = await _restartFromSources(reason: reason,); result = await _restartFromSources(reason: reason);
restartTimer.stop();
if (!result.isOk) { if (!result.isOk) {
restartEvent = 'restart-failed'; restartEvent = 'restart-failed';
} else {
HotEvent('restart',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: true,
reason: reason,
fastReassemble: null,
overallTimeInMs: restartTimer.elapsed.inMilliseconds,
syncedBytes: result.updateFSReport?.syncedBytes,
invalidatedSourcesCount: result.updateFSReport?.invalidatedSourcesCount,
transferTimeInMs: result.updateFSReport?.transferDuration?.inMilliseconds,
compileTimeInMs: result.updateFSReport?.compileDuration?.inMilliseconds,
findInvalidatedTimeInMs: result.updateFSReport?.findInvalidatedDuration?.inMilliseconds,
scannedSourcesCount: result.updateFSReport?.scannedSourcesCount,
).send();
} }
} on vm_service.SentinelException catch (err, st) { } on vm_service.SentinelException catch (err, st) {
restartEvent = 'exception'; restartEvent = 'exception';
...@@ -698,6 +767,9 @@ class HotRunner extends ResidentRunner { ...@@ -698,6 +767,9 @@ class HotRunner extends ResidentRunner {
restartEvent = 'exception'; restartEvent = 'exception';
return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true); return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true);
} finally { } finally {
// The `restartEvent` variable will be null if restart succeeded. We will
// only handle the case when it failed here.
if (restartEvent != null) {
HotEvent(restartEvent, HotEvent(restartEvent,
targetPlatform: targetPlatform, targetPlatform: targetPlatform,
sdkName: sdkName, sdkName: sdkName,
...@@ -706,6 +778,7 @@ class HotRunner extends ResidentRunner { ...@@ -706,6 +778,7 @@ class HotRunner extends ResidentRunner {
reason: reason, reason: reason,
fastReassemble: null, fastReassemble: null,
).send(); ).send();
}
status?.cancel(); status?.cancel();
} }
return result; return result;
...@@ -772,24 +845,6 @@ class HotRunner extends ResidentRunner { ...@@ -772,24 +845,6 @@ class HotRunner extends ResidentRunner {
return result; return result;
} }
Future<List<Future<vm_service.ReloadReport>>> _reloadDeviceSources(
FlutterDevice device,
String entryPath, {
bool pause = false,
}) async {
final String deviceEntryUri = device.devFS.baseUri
.resolve(entryPath).toString();
final vm_service.VM vm = await device.vmService.service.getVM();
return <Future<vm_service.ReloadReport>>[
for (final vm_service.IsolateRef isolateRef in vm.isolates)
device.vmService.service.reloadSources(
isolateRef.id,
pause: pause,
rootLibUri: deviceEntryUri,
)
];
}
Future<OperationResult> _reloadSources({ Future<OperationResult> _reloadSources({
String targetPlatform, String targetPlatform,
String sdkName, String sdkName,
...@@ -809,7 +864,7 @@ class HotRunner extends ResidentRunner { ...@@ -809,7 +864,7 @@ class HotRunner extends ResidentRunner {
} }
} }
final Stopwatch reloadTimer = Stopwatch()..start(); final Stopwatch reloadTimer = _stopwatchFactory.createStopwatch('reloadSources:reload')..start();
final Stopwatch devFSTimer = Stopwatch()..start(); final Stopwatch devFSTimer = Stopwatch()..start();
final UpdateFSReport updatedDevFS = await _updateDevFS(); final UpdateFSReport updatedDevFS = await _updateDevFS();
// Record time it took to synchronize to DevFS. // Record time it took to synchronize to DevFS.
...@@ -819,9 +874,12 @@ class HotRunner extends ResidentRunner { ...@@ -819,9 +874,12 @@ class HotRunner extends ResidentRunner {
return OperationResult(1, 'DevFS synchronization failed'); return OperationResult(1, 'DevFS synchronization failed');
} }
String reloadMessage = 'Reloaded 0 libraries'; String reloadMessage = 'Reloaded 0 libraries';
final Stopwatch reloadVMTimer = _stopwatchFactory.createStopwatch('reloadSources:vm')..start();
final Map<String, Object> firstReloadDetails = <String, Object>{}; final Map<String, Object> firstReloadDetails = <String, Object>{};
if (updatedDevFS.invalidatedSourcesCount > 0) { if (updatedDevFS.invalidatedSourcesCount > 0) {
final OperationResult result = await _reloadSourcesHelper( final OperationResult result = await _reloadSourcesHelper(
this,
flutterDevices,
pause, pause,
firstReloadDetails, firstReloadDetails,
targetPlatform, targetPlatform,
...@@ -836,107 +894,25 @@ class HotRunner extends ResidentRunner { ...@@ -836,107 +894,25 @@ class HotRunner extends ResidentRunner {
} else { } else {
_addBenchmarkData('hotReloadVMReloadMilliseconds', 0); _addBenchmarkData('hotReloadVMReloadMilliseconds', 0);
} }
reloadVMTimer.stop();
final Stopwatch reassembleTimer = Stopwatch()..start();
await _evictDirtyAssets(); await _evictDirtyAssets();
// Check if any isolates are paused and reassemble those that aren't. final Stopwatch reassembleTimer = _stopwatchFactory.createStopwatch('reloadSources:reassemble')..start();
final Map<FlutterView, FlutterVmService> reassembleViews = <FlutterView, FlutterVmService>{};
final List<Future<void>> reassembleFutures = <Future<void>>[]; final ReassembleResult reassembleResult = await _reassembleHelper(
String serviceEventKind; flutterDevices,
int pausedIsolatesFound = 0; viewCache,
bool failedReassemble = false; onSlow,
for (final FlutterDevice device in flutterDevices) { reloadMessage,
final List<FlutterView> views = viewCache[device]; updatedDevFS.fastReassembleClassName,
for (final FlutterView view in views) {
// Check if the isolate is paused, and if so, don't reassemble. Ignore the
// PostPauseEvent event - the client requesting the pause will resume the app.
final vm_service.Isolate isolate = await device.vmService
.getIsolateOrNull(view.uiIsolate.id);
final vm_service.Event pauseEvent = isolate?.pauseEvent;
if (pauseEvent != null
&& isPauseEvent(pauseEvent.kind)
&& pauseEvent.kind != vm_service.EventKind.kPausePostRequest) {
pausedIsolatesFound += 1;
if (serviceEventKind == null) {
serviceEventKind = pauseEvent.kind;
} else if (serviceEventKind != pauseEvent.kind) {
serviceEventKind = ''; // many kinds
}
} else {
reassembleViews[view] = device.vmService;
// If the tool identified a change in a single widget, do a fast instead
// of a full reassemble.
Future<void> reassembleWork;
if (updatedDevFS.fastReassembleClassName != null) {
reassembleWork = device.vmService.flutterFastReassemble(
isolateId: view.uiIsolate.id,
className: updatedDevFS.fastReassembleClassName,
);
} else {
reassembleWork = device.vmService.flutterReassemble(
isolateId: view.uiIsolate.id,
); );
} shouldReportReloadTime = reassembleResult.shouldReportReloadTime;
reassembleFutures.add(reassembleWork.catchError((dynamic error) { if (reassembleResult.reassembleViews.isEmpty) {
failedReassemble = true;
globals.printError('Reassembling ${view.uiIsolate.name} failed: $error');
}, test: (dynamic error) => error is Exception));
}
}
}
if (pausedIsolatesFound > 0) {
if (onSlow != null) {
onSlow('${_describePausedIsolates(pausedIsolatesFound, serviceEventKind)}; interface might not update.');
}
if (reassembleViews.isEmpty) {
globals.printTrace('Skipping reassemble because all isolates are paused.');
return OperationResult(OperationResult.ok.code, reloadMessage); return OperationResult(OperationResult.ok.code, reloadMessage);
} }
}
assert(reassembleViews.isNotEmpty);
globals.printTrace('Reassembling application');
final Future<void> reassembleFuture = Future.wait<void>(reassembleFutures);
await reassembleFuture.timeout(
const Duration(seconds: 2),
onTimeout: () async {
if (pausedIsolatesFound > 0) {
shouldReportReloadTime = false;
return; // probably no point waiting, they're probably deadlocked and we've already warned.
}
// Check if any isolate is newly paused.
globals.printTrace('This is taking a long time; will now check for paused isolates.');
int postReloadPausedIsolatesFound = 0;
String serviceEventKind;
for (final FlutterView view in reassembleViews.keys) {
final vm_service.Isolate isolate = await reassembleViews[view]
.getIsolateOrNull(view.uiIsolate.id);
if (isolate == null) {
continue;
}
if (isolate.pauseEvent != null && isPauseEvent(isolate.pauseEvent.kind)) {
postReloadPausedIsolatesFound += 1;
if (serviceEventKind == null) {
serviceEventKind = isolate.pauseEvent.kind;
} else if (serviceEventKind != isolate.pauseEvent.kind) {
serviceEventKind = ''; // many kinds
}
}
}
globals.printTrace('Found $postReloadPausedIsolatesFound newly paused isolate(s).');
if (postReloadPausedIsolatesFound == 0) {
await reassembleFuture; // must just be taking a long time... keep waiting!
return;
}
shouldReportReloadTime = false;
if (onSlow != null) {
onSlow('${_describePausedIsolates(postReloadPausedIsolatesFound, serviceEventKind)}.');
}
},
);
// Record time it took for Flutter to reassemble the application. // Record time it took for Flutter to reassemble the application.
reassembleTimer.stop();
_addBenchmarkData('hotReloadFlutterReassembleMilliseconds', reassembleTimer.elapsed.inMilliseconds); _addBenchmarkData('hotReloadFlutterReassembleMilliseconds', reassembleTimer.elapsed.inMilliseconds);
reloadTimer.stop(); reloadTimer.stop();
...@@ -961,10 +937,15 @@ class HotRunner extends ResidentRunner { ...@@ -961,10 +937,15 @@ class HotRunner extends ResidentRunner {
syncedProceduresCount: firstReloadDetails['receivedProceduresCount'] as int ?? 0, syncedProceduresCount: firstReloadDetails['receivedProceduresCount'] as int ?? 0,
syncedBytes: updatedDevFS.syncedBytes, syncedBytes: updatedDevFS.syncedBytes,
invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount, invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount,
transferTimeInMs: devFSTimer.elapsed.inMilliseconds, transferTimeInMs: updatedDevFS.transferDuration.inMilliseconds,
fastReassemble: featureFlags.isSingleWidgetReloadEnabled fastReassemble: featureFlags.isSingleWidgetReloadEnabled
? updatedDevFS.fastReassembleClassName != null ? updatedDevFS.fastReassembleClassName != null
: null, : null,
compileTimeInMs: updatedDevFS.compileDuration.inMilliseconds,
findInvalidatedTimeInMs: updatedDevFS.findInvalidatedDuration.inMilliseconds,
scannedSourcesCount: updatedDevFS.scannedSourcesCount,
reassembleTimeInMs: reassembleTimer.elapsed.inMilliseconds,
reloadVMTimeInMs: reloadVMTimer.elapsed.inMilliseconds,
).send(); ).send();
if (shouldReportReloadTime) { if (shouldReportReloadTime) {
...@@ -973,23 +954,124 @@ class HotRunner extends ResidentRunner { ...@@ -973,23 +954,124 @@ class HotRunner extends ResidentRunner {
_addBenchmarkData('hotReloadMillisecondsToFrame', reloadInMs); _addBenchmarkData('hotReloadMillisecondsToFrame', reloadInMs);
} }
// Only report timings if we reloaded a single view without any errors. // Only report timings if we reloaded a single view without any errors.
if ((reassembleViews.length == 1) && !failedReassemble && shouldReportReloadTime) { if ((reassembleResult.reassembleViews.length == 1) && !reassembleResult.failedReassemble && shouldReportReloadTime) {
globals.flutterUsage.sendTiming('hot', 'reload', reloadDuration); globals.flutterUsage.sendTiming('hot', 'reload', reloadDuration);
} }
return OperationResult( return OperationResult(
failedReassemble ? 1 : OperationResult.ok.code, reassembleResult.failedReassemble ? 1 : OperationResult.ok.code,
reloadMessage, reloadMessage,
); );
} }
Future<OperationResult> _reloadSourcesHelper( @override
void printHelp({ @required bool details }) {
globals.printStatus('Flutter run key commands.');
commandHelp.r.print();
if (supportsRestart) {
commandHelp.R.print();
}
if (details) {
printHelpDetails();
commandHelp.hWithDetails.print();
} else {
commandHelp.hWithoutDetails.print();
}
if (_didAttach) {
commandHelp.d.print();
}
commandHelp.c.print();
commandHelp.q.print();
globals.printStatus('');
if (debuggingOptions.buildInfo.nullSafetyMode == NullSafetyMode.sound) {
globals.printStatus('💪 Running with sound null safety 💪', emphasis: true);
} else {
globals.printStatus(
'Running with unsound null safety',
emphasis: true,
);
globals.printStatus(
'For more information see https://dart.dev/null-safety/unsound-null-safety',
);
}
globals.printStatus('');
printDebuggerList();
}
Future<void> _evictDirtyAssets() async {
final List<Future<Map<String, dynamic>>> futures = <Future<Map<String, dynamic>>>[];
for (final FlutterDevice device in flutterDevices) {
if (device.devFS.assetPathsToEvict.isEmpty) {
continue;
}
final List<FlutterView> views = await device.vmService.getFlutterViews();
if (views.first.uiIsolate == null) {
globals.printError('Application isolate not found for $device');
continue;
}
for (final String assetPath in device.devFS.assetPathsToEvict) {
futures.add(
device.vmService
.flutterEvictAsset(
assetPath,
isolateId: views.first.uiIsolate.id,
)
);
}
device.devFS.assetPathsToEvict.clear();
}
return Future.wait<Map<String, dynamic>>(futures);
}
@override
Future<void> cleanupAfterSignal() async {
await stopEchoingDeviceLog();
await hotRunnerConfig.runPreShutdownOperations();
if (_didAttach) {
appFinished();
} else {
await exitApp();
}
}
@override
Future<void> preExit() async {
await _cleanupDevFS();
await hotRunnerConfig.runPreShutdownOperations();
await super.preExit();
}
@override
Future<void> cleanupAtFinish() async {
for (final FlutterDevice flutterDevice in flutterDevices) {
await flutterDevice.device.dispose();
}
await _cleanupDevFS();
await residentDevtoolsHandler.shutdown();
await stopEchoingDeviceLog();
}
}
typedef ReloadSourcesHelper = Future<OperationResult> Function(
HotRunner hotRunner,
List<FlutterDevice> flutterDevices,
bool pause, bool pause,
Map<String, dynamic> firstReloadDetails, Map<String, dynamic> firstReloadDetails,
String targetPlatform, String targetPlatform,
String sdkName, String sdkName,
bool emulator, bool emulator,
String reason, String reason,
) async { );
Future<OperationResult> _defaultReloadSourcesHelper(
HotRunner hotRunner,
List<FlutterDevice> flutterDevices,
bool pause,
Map<String, dynamic> firstReloadDetails,
String targetPlatform,
String sdkName,
bool emulator,
String reason,
) async {
final Stopwatch vmReloadTimer = Stopwatch()..start(); final Stopwatch vmReloadTimer = Stopwatch()..start();
const String entryPath = 'main.dart.incremental.dill'; const String entryPath = 'main.dart.incremental.dill';
final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[]; final List<Future<DeviceReloadReport>> allReportsFutures = <Future<DeviceReloadReport>>[];
...@@ -1008,7 +1090,7 @@ class HotRunner extends ResidentRunner { ...@@ -1008,7 +1090,7 @@ class HotRunner extends ResidentRunner {
// Don't print errors because they will be printed further down when // Don't print errors because they will be printed further down when
// `validateReloadReport` is called again. // `validateReloadReport` is called again.
await device.updateReloadStatus( await device.updateReloadStatus(
validateReloadReport(firstReport, printErrors: false), HotRunner.validateReloadReport(firstReport, printErrors: false),
); );
return DeviceReloadReport(device, reports); return DeviceReloadReport(device, reports);
}, },
...@@ -1016,7 +1098,7 @@ class HotRunner extends ResidentRunner { ...@@ -1016,7 +1098,7 @@ class HotRunner extends ResidentRunner {
} }
final List<DeviceReloadReport> reports = await Future.wait(allReportsFutures); final List<DeviceReloadReport> reports = await Future.wait(allReportsFutures);
final vm_service.ReloadReport reloadReport = reports.first.reports[0]; final vm_service.ReloadReport reloadReport = reports.first.reports[0];
if (!validateReloadReport(reloadReport)) { if (!HotRunner.validateReloadReport(reloadReport)) {
// Reload failed. // Reload failed.
HotEvent('reload-reject', HotEvent('reload-reject',
targetPlatform: targetPlatform, targetPlatform: targetPlatform,
...@@ -1028,7 +1110,7 @@ class HotRunner extends ResidentRunner { ...@@ -1028,7 +1110,7 @@ class HotRunner extends ResidentRunner {
).send(); ).send();
// Reset devFS lastCompileTime to ensure the file will still be marked // Reset devFS lastCompileTime to ensure the file will still be marked
// as dirty on subsequent reloads. // as dirty on subsequent reloads.
_resetDevFSCompileTime(); _resetDevFSCompileTime(flutterDevices);
final ReloadReportContents contents = ReloadReportContents.fromReloadReport(reloadReport); final ReloadReportContents contents = ReloadReportContents.fromReloadReport(reloadReport);
return OperationResult(1, 'Reload rejected: ${contents.notices.join("\n")}'); return OperationResult(1, 'Reload rejected: ${contents.notices.join("\n")}');
} }
...@@ -1041,11 +1123,158 @@ class HotRunner extends ResidentRunner { ...@@ -1041,11 +1123,158 @@ class HotRunner extends ResidentRunner {
globals.printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries'); globals.printTrace('reloaded $loadedLibraryCount of $finalLibraryCount libraries');
// reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries'; // reloadMessage = 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries';
// Record time it took for the VM to reload the sources. // Record time it took for the VM to reload the sources.
_addBenchmarkData('hotReloadVMReloadMilliseconds', vmReloadTimer.elapsed.inMilliseconds); hotRunner._addBenchmarkData('hotReloadVMReloadMilliseconds', vmReloadTimer.elapsed.inMilliseconds);
return OperationResult(0, 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries'); return OperationResult(0, 'Reloaded $loadedLibraryCount of $finalLibraryCount libraries');
}
Future<List<Future<vm_service.ReloadReport>>> _reloadDeviceSources(
FlutterDevice device,
String entryPath, {
bool pause = false,
}) async {
final String deviceEntryUri = device.devFS.baseUri
.resolve(entryPath).toString();
final vm_service.VM vm = await device.vmService.service.getVM();
return <Future<vm_service.ReloadReport>>[
for (final vm_service.IsolateRef isolateRef in vm.isolates)
device.vmService.service.reloadSources(
isolateRef.id,
pause: pause,
rootLibUri: deviceEntryUri,
)
];
}
void _resetDevFSCompileTime(List<FlutterDevice> flutterDevices) {
for (final FlutterDevice device in flutterDevices) {
device.devFS.resetLastCompiled();
} }
}
@visibleForTesting
class ReassembleResult {
ReassembleResult(this.reassembleViews, this.failedReassemble, this.shouldReportReloadTime);
final Map<FlutterView, FlutterVmService> reassembleViews;
final bool failedReassemble;
final bool shouldReportReloadTime;
}
String _describePausedIsolates(int pausedIsolatesFound, String serviceEventKind) { typedef ReassembleHelper = Future<ReassembleResult> Function(
List<FlutterDevice> flutterDevices,
Map<FlutterDevice, List<FlutterView>> viewCache,
void Function(String message) onSlow,
String reloadMessage,
String fastReassembleClassName,
);
Future<ReassembleResult> _defaultReassembleHelper(
List<FlutterDevice> flutterDevices,
Map<FlutterDevice, List<FlutterView>> viewCache,
void Function(String message) onSlow,
String reloadMessage,
String fastReassembleClassName,
) async {
// Check if any isolates are paused and reassemble those that aren't.
final Map<FlutterView, FlutterVmService> reassembleViews = <FlutterView, FlutterVmService>{};
final List<Future<void>> reassembleFutures = <Future<void>>[];
String serviceEventKind;
int pausedIsolatesFound = 0;
bool failedReassemble = false;
bool shouldReportReloadTime = true;
for (final FlutterDevice device in flutterDevices) {
final List<FlutterView> views = viewCache[device];
for (final FlutterView view in views) {
// Check if the isolate is paused, and if so, don't reassemble. Ignore the
// PostPauseEvent event - the client requesting the pause will resume the app.
final vm_service.Isolate isolate = await device.vmService
.getIsolateOrNull(view.uiIsolate.id);
final vm_service.Event pauseEvent = isolate?.pauseEvent;
if (pauseEvent != null
&& isPauseEvent(pauseEvent.kind)
&& pauseEvent.kind != vm_service.EventKind.kPausePostRequest) {
pausedIsolatesFound += 1;
if (serviceEventKind == null) {
serviceEventKind = pauseEvent.kind;
} else if (serviceEventKind != pauseEvent.kind) {
serviceEventKind = ''; // many kinds
}
} else {
reassembleViews[view] = device.vmService;
// If the tool identified a change in a single widget, do a fast instead
// of a full reassemble.
Future<void> reassembleWork;
if (fastReassembleClassName != null) {
reassembleWork = device.vmService.flutterFastReassemble(
isolateId: view.uiIsolate.id,
className: fastReassembleClassName,
);
} else {
reassembleWork = device.vmService.flutterReassemble(
isolateId: view.uiIsolate.id,
);
}
reassembleFutures.add(reassembleWork.catchError((dynamic error) {
failedReassemble = true;
globals.printError('Reassembling ${view.uiIsolate.name} failed: $error');
}, test: (dynamic error) => error is Exception));
}
}
}
if (pausedIsolatesFound > 0) {
if (onSlow != null) {
onSlow('${_describePausedIsolates(pausedIsolatesFound, serviceEventKind)}; interface might not update.');
}
if (reassembleViews.isEmpty) {
globals.printTrace('Skipping reassemble because all isolates are paused.');
return ReassembleResult(reassembleViews, failedReassemble, shouldReportReloadTime);
}
}
assert(reassembleViews.isNotEmpty);
globals.printTrace('Reassembling application');
final Future<void> reassembleFuture = Future.wait<void>(reassembleFutures);
await reassembleFuture.timeout(
const Duration(seconds: 2),
onTimeout: () async {
if (pausedIsolatesFound > 0) {
shouldReportReloadTime = false;
return; // probably no point waiting, they're probably deadlocked and we've already warned.
}
// Check if any isolate is newly paused.
globals.printTrace('This is taking a long time; will now check for paused isolates.');
int postReloadPausedIsolatesFound = 0;
String serviceEventKind;
for (final FlutterView view in reassembleViews.keys) {
final vm_service.Isolate isolate = await reassembleViews[view]
.getIsolateOrNull(view.uiIsolate.id);
if (isolate == null) {
continue;
}
if (isolate.pauseEvent != null && isPauseEvent(isolate.pauseEvent.kind)) {
postReloadPausedIsolatesFound += 1;
if (serviceEventKind == null) {
serviceEventKind = isolate.pauseEvent.kind;
} else if (serviceEventKind != isolate.pauseEvent.kind) {
serviceEventKind = ''; // many kinds
}
}
}
globals.printTrace('Found $postReloadPausedIsolatesFound newly paused isolate(s).');
if (postReloadPausedIsolatesFound == 0) {
await reassembleFuture; // must just be taking a long time... keep waiting!
return;
}
shouldReportReloadTime = false;
if (onSlow != null) {
onSlow('${_describePausedIsolates(postReloadPausedIsolatesFound, serviceEventKind)}.');
}
},
);
return ReassembleResult(reassembleViews, failedReassemble, shouldReportReloadTime);
}
String _describePausedIsolates(int pausedIsolatesFound, String serviceEventKind) {
assert(pausedIsolatesFound > 0); assert(pausedIsolatesFound > 0);
final StringBuffer message = StringBuffer(); final StringBuffer message = StringBuffer();
bool plural; bool plural;
...@@ -1083,94 +1312,6 @@ class HotRunner extends ResidentRunner { ...@@ -1083,94 +1312,6 @@ class HotRunner extends ResidentRunner {
message.write('paused'); message.write('paused');
} }
return message.toString(); return message.toString();
}
@override
void printHelp({ @required bool details }) {
globals.printStatus('Flutter run key commands.');
commandHelp.r.print();
if (supportsRestart) {
commandHelp.R.print();
}
if (details) {
printHelpDetails();
commandHelp.hWithDetails.print();
} else {
commandHelp.hWithoutDetails.print();
}
if (_didAttach) {
commandHelp.d.print();
}
commandHelp.c.print();
commandHelp.q.print();
globals.printStatus('');
if (debuggingOptions.buildInfo.nullSafetyMode == NullSafetyMode.sound) {
globals.printStatus('💪 Running with sound null safety 💪', emphasis: true);
} else {
globals.printStatus(
'Running with unsound null safety',
emphasis: true,
);
globals.printStatus(
'For more information see https://dart.dev/null-safety/unsound-null-safety',
);
}
globals.printStatus('');
printDebuggerList();
}
Future<void> _evictDirtyAssets() async {
final List<Future<Map<String, dynamic>>> futures = <Future<Map<String, dynamic>>>[];
for (final FlutterDevice device in flutterDevices) {
if (device.devFS.assetPathsToEvict.isEmpty) {
continue;
}
final List<FlutterView> views = await device.vmService.getFlutterViews();
if (views.first.uiIsolate == null) {
globals.printError('Application isolate not found for $device');
continue;
}
for (final String assetPath in device.devFS.assetPathsToEvict) {
futures.add(
device.vmService
.flutterEvictAsset(
assetPath,
isolateId: views.first.uiIsolate.id,
)
);
}
device.devFS.assetPathsToEvict.clear();
}
return Future.wait<Map<String, dynamic>>(futures);
}
@override
Future<void> cleanupAfterSignal() async {
await stopEchoingDeviceLog();
await hotRunnerConfig.runPreShutdownOperations();
if (_didAttach) {
appFinished();
} else {
await exitApp();
}
}
@override
Future<void> preExit() async {
await _cleanupDevFS();
await hotRunnerConfig.runPreShutdownOperations();
await super.preExit();
}
@override
Future<void> cleanupAtFinish() async {
for (final FlutterDevice flutterDevice in flutterDevices) {
await flutterDevice.device.dispose();
}
await _cleanupDevFS();
await residentDevtoolsHandler.shutdown();
await stopEchoingDeviceLog();
}
} }
/// The result of an invalidation check from [ProjectFileInvalidator]. /// The result of an invalidation check from [ProjectFileInvalidator].
...@@ -1225,7 +1366,7 @@ class ProjectFileInvalidator { ...@@ -1225,7 +1366,7 @@ class ProjectFileInvalidator {
assert(urisToMonitor.isEmpty); assert(urisToMonitor.isEmpty);
return InvalidationResult( return InvalidationResult(
packageConfig: packageConfig, packageConfig: packageConfig,
uris: <Uri>[] uris: <Uri>[],
); );
} }
......
...@@ -244,7 +244,7 @@ void main() { ...@@ -244,7 +244,7 @@ void main() {
); );
final VerboseLogger verboseLogger = VerboseLogger( final VerboseLogger verboseLogger = VerboseLogger(
mockLogger, mockLogger,
stopwatchFactory: FakeStopwatchFactory(fakeStopWatch), stopwatchFactory: FakeStopwatchFactory(stopwatch: fakeStopWatch),
); );
verboseLogger.printStatus('Hey Hey Hey Hey'); verboseLogger.printStatus('Hey Hey Hey Hey');
...@@ -266,7 +266,7 @@ void main() { ...@@ -266,7 +266,7 @@ void main() {
outputPreferences: OutputPreferences.test(showColor: true), outputPreferences: OutputPreferences.test(showColor: true),
); );
final VerboseLogger verboseLogger = VerboseLogger( final VerboseLogger verboseLogger = VerboseLogger(
mockLogger, stopwatchFactory: FakeStopwatchFactory(fakeStopWatch), mockLogger, stopwatchFactory: FakeStopwatchFactory(stopwatch: fakeStopWatch),
); );
verboseLogger.printStatus('Hey Hey Hey Hey'); verboseLogger.printStatus('Hey Hey Hey Hey');
...@@ -377,7 +377,7 @@ void main() { ...@@ -377,7 +377,7 @@ void main() {
mockStopwatch = FakeStopwatch(); mockStopwatch = FakeStopwatch();
mockStdio = FakeStdio(); mockStdio = FakeStdio();
called = 0; called = 0;
stopwatchFactory = FakeStopwatchFactory(mockStopwatch); stopwatchFactory = FakeStopwatchFactory(stopwatch: mockStopwatch);
}); });
List<String> outputStdout() => mockStdio.writtenToStdout.join('').split('\n'); List<String> outputStdout() => mockStdio.writtenToStdout.join('').split('\n');
...@@ -938,7 +938,7 @@ void main() { ...@@ -938,7 +938,7 @@ void main() {
), ),
stdio: fakeStdio, stdio: fakeStdio,
outputPreferences: OutputPreferences.test(showColor: false), outputPreferences: OutputPreferences.test(showColor: false),
stopwatchFactory: FakeStopwatchFactory(fakeStopwatch), stopwatchFactory: FakeStopwatchFactory(stopwatch: fakeStopwatch),
); );
final Status status = logger.startProgress( final Status status = logger.startProgress(
'Hello', 'Hello',
...@@ -1062,53 +1062,6 @@ void main() { ...@@ -1062,53 +1062,6 @@ void main() {
}); });
} }
class FakeStopwatch implements Stopwatch {
@override
bool get isRunning => _isRunning;
bool _isRunning = false;
@override
void start() => _isRunning = true;
@override
void stop() => _isRunning = false;
@override
Duration elapsed = Duration.zero;
@override
int get elapsedMicroseconds => elapsed.inMicroseconds;
@override
int get elapsedMilliseconds => elapsed.inMilliseconds;
@override
int get elapsedTicks => elapsed.inMilliseconds;
@override
int get frequency => 1000;
@override
void reset() {
_isRunning = false;
elapsed = Duration.zero;
}
@override
String toString() => '$runtimeType $elapsed $isRunning';
}
class FakeStopwatchFactory implements StopwatchFactory {
FakeStopwatchFactory([this.stopwatch]);
Stopwatch stopwatch;
@override
Stopwatch createStopwatch() {
return stopwatch ?? FakeStopwatch();
}
}
/// A fake [Logger] that throws the [Invocation] for any method call. /// A fake [Logger] that throws the [Invocation] for any method call.
class FakeLogger implements Logger { class FakeLogger implements Logger {
@override @override
......
...@@ -425,6 +425,51 @@ void main() { ...@@ -425,6 +425,51 @@ void main() {
Uri.parse('goodbye'): DevFSFileContent(file), Uri.parse('goodbye'): DevFSFileContent(file),
}, Uri.parse('/foo/bar/devfs/')), throwsA(isA<DevFSException>())); }, Uri.parse('/foo/bar/devfs/')), throwsA(isA<DevFSException>()));
}); });
testWithoutContext('DevFS correctly records the elapsed time', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
// final FakeDevFSWriter writer = FakeDevFSWriter();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: BufferLogger.test(),
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
stopwatchFactory: FakeStopwatchFactory(stopwatches: <String, Stopwatch>{
'compile': FakeStopwatch()..elapsed = const Duration(seconds: 3),
'transfer': FakeStopwatch()..elapsed = const Duration(seconds: 5),
}),
);
await devFS.create();
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
fileSystem.file('lib/foo.txt.dill').createSync(recursive: true);
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
};
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/main.dart'),
generator: residentCompiler,
dillOutputPath: 'lib/foo.dill',
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.success, true);
expect(report.compileDuration, const Duration(seconds: 3));
expect(report.transferDuration, const Duration(seconds: 5));
});
} }
class FakeResidentCompiler extends Fake implements ResidentCompiler { class FakeResidentCompiler extends Fake implements ResidentCompiler {
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -14,17 +15,20 @@ import 'package:flutter_tools/src/build_info.dart'; ...@@ -14,17 +15,20 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_devtools_handler.dart'; import 'package:flutter_tools/src/resident_devtools_handler.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart' as vm_service;
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/fake_vm_services.dart'; import '../src/fake_vm_services.dart';
import '../src/fakes.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1', id: '1',
...@@ -146,9 +150,11 @@ void main() { ...@@ -146,9 +150,11 @@ void main() {
group('hotRestart', () { group('hotRestart', () {
final FakeResidentCompiler residentCompiler = FakeResidentCompiler(); final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
FileSystem fileSystem; FileSystem fileSystem;
TestUsage testUsage;
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
testUsage = TestUsage();
}); });
testUsingContext('setup function fails', () async { testUsingContext('setup function fails', () async {
...@@ -228,6 +234,158 @@ void main() { ...@@ -228,6 +234,158 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
}); });
testUsingContext('correctly tracks time spent for analytics for hot restart', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport(
success: true,
invalidatedSourcesCount: 2,
syncedBytes: 4,
scannedSourcesCount: 8,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
stopwatches: <String, Stopwatch>{
'fullRestartHelper': FakeStopwatch()..elapsed = const Duration(seconds: 64),
'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 128),
},
);
(fakeFlutterDevice.devFS as FakeDevFs).baseUri = Uri.parse('file:///base_uri');
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
stopwatchFactory: fakeStopwatchFactory,
).restart(fullRestart: true);
expect(result.isOk, true);
expect(testUsage.events, <TestUsageEvent>[
const TestUsageEvent('hot', 'restart', parameters: CustomDimensions(
hotEventTargetPlatform: 'flutter-tester',
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: true,
hotEventOverallTimeInMs: 64000,
hotEventSyncedBytes: 4,
hotEventInvalidatedSourcesCount: 2,
hotEventTransferTimeInMs: 32000,
hotEventCompileTimeInMs: 16000,
hotEventFindInvalidatedTimeInMs: 128000,
hotEventScannedSourcesCount: 8,
)),
]);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
testUsingContext('correctly tracks time spent for analytics for hot reload', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReport = UpdateFSReport(
success: true,
invalidatedSourcesCount: 6,
syncedBytes: 8,
scannedSourcesCount: 16,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
stopwatches: <String, Stopwatch>{
'updateDevFS': FakeStopwatch()..elapsed = const Duration(seconds: 64),
'reloadSources:reload': FakeStopwatch()..elapsed = const Duration(seconds: 128),
'reloadSources:reassemble': FakeStopwatch()..elapsed = const Duration(seconds: 256),
'reloadSources:vm': FakeStopwatch()..elapsed = const Duration(seconds: 512),
},
);
(fakeFlutterDevice.devFS as FakeDevFs).baseUri = Uri.parse('file:///base_uri');
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
stopwatchFactory: fakeStopwatchFactory,
reloadSourcesHelper: (
HotRunner hotRunner,
List<FlutterDevice> flutterDevices,
bool pause,
Map<String, dynamic> firstReloadDetails,
String targetPlatform,
String sdkName,
bool emulator,
String reason,
) async {
firstReloadDetails['finalLibraryCount'] = 2;
firstReloadDetails['receivedLibraryCount'] = 3;
firstReloadDetails['receivedClassesCount'] = 4;
firstReloadDetails['receivedProceduresCount'] = 5;
return OperationResult.ok;
},
reassembleHelper: (
List<FlutterDevice> flutterDevices,
Map<FlutterDevice, List<FlutterView>> viewCache,
void Function(String message) onSlow,
String reloadMessage,
String fastReassembleClassName,
) async => ReassembleResult(
<FlutterView, FlutterVmService>{null: null},
false,
true,
),
).restart(fullRestart: false);
expect(result.isOk, true);
expect(testUsage.events, <TestUsageEvent>[
const TestUsageEvent('hot', 'reload', parameters: CustomDimensions(
hotEventFinalLibraryCount: 2,
hotEventSyncedLibraryCount: 3,
hotEventSyncedClassesCount: 4,
hotEventSyncedProceduresCount: 5,
hotEventSyncedBytes: 8,
hotEventInvalidatedSourcesCount: 6,
hotEventTransferTimeInMs: 32000,
hotEventOverallTimeInMs: 128000,
hotEventTargetPlatform: 'flutter-tester',
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
hotEventCompileTimeInMs: 16000,
hotEventFindInvalidatedTimeInMs: 64000,
hotEventScannedSourcesCount: 16,
hotEventReassembleTimeInMs: 256000,
hotEventReloadVMTimeInMs: 512000,
)),
]);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
}); });
group('hot attach', () { group('hot attach', () {
...@@ -299,6 +457,21 @@ void main() { ...@@ -299,6 +457,21 @@ void main() {
class FakeDevFs extends Fake implements DevFS { class FakeDevFs extends Fake implements DevFS {
@override @override
Future<void> destroy() async { } Future<void> destroy() async { }
@override
List<Uri> sources = <Uri>[];
@override
DateTime lastCompiled;
@override
PackageConfig lastPackageConfig;
@override
Set<String> assetPathsToEvict = <String>{};
@override
Uri baseUri;
} }
class FakeDevice extends Fake implements Device { class FakeDevice extends Fake implements Device {
...@@ -325,6 +498,9 @@ class FakeDevice extends Fake implements Device { ...@@ -325,6 +498,9 @@ class FakeDevice extends Fake implements Device {
@override @override
Future<bool> get isLocalEmulator async => false; Future<bool> get isLocalEmulator async => false;
@override
String get name => 'Fake Device';
@override @override
Future<bool> stopApp( Future<bool> stopApp(
covariant ApplicationPackage app, { covariant ApplicationPackage app, {
...@@ -343,6 +519,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { ...@@ -343,6 +519,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeFlutterDevice(this.device); FakeFlutterDevice(this.device);
bool stoppedEchoingDeviceLog = false; bool stoppedEchoingDeviceLog = false;
UpdateFSReport updateDevFSReport;
@override @override
final FakeDevice device; final FakeDevice device;
...@@ -354,6 +531,28 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { ...@@ -354,6 +531,28 @@ class FakeFlutterDevice extends Fake implements FlutterDevice {
@override @override
DevFS devFS = FakeDevFs(); DevFS devFS = FakeDevFs();
@override
FlutterVmService get vmService => FakeFlutterVmService();
@override
ResidentCompiler generator;
@override
Future<UpdateFSReport> updateDevFS({
Uri mainUri,
String target,
AssetBundle bundle,
DateTime firstBuildTime,
bool bundleFirstUpload = false,
bool bundleDirty = false,
bool fullRestart = false,
String projectRootPath,
String pathToReload,
@required String dillOutputPath,
@required List<Uri> invalidatedFiles,
@required PackageConfig packageConfig,
}) async => updateDevFSReport;
} }
class TestFlutterDevice extends FlutterDevice { class TestFlutterDevice extends FlutterDevice {
...@@ -405,3 +604,23 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler { ...@@ -405,3 +604,23 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
@override @override
void accept() {} void accept() {}
} }
class FakeFlutterVmService extends Fake implements FlutterVmService {
@override
vm_service.VmService get service => FakeVmService();
@override
Future<List<FlutterView>> getFlutterViews({bool returnEarly = false, Duration delay = const Duration(milliseconds: 50)}) async {
return <FlutterView>[];
}
}
class FakeVmService extends Fake implements vm_service.VmService {
@override
Future<vm_service.VM> getVM() async => FakeVm();
}
class FakeVm extends Fake implements vm_service.VM {
@override
List<vm_service.IsolateRef> get isolates => <vm_service.IsolateRef>[];
}
...@@ -535,3 +535,56 @@ class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { ...@@ -535,3 +535,56 @@ class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override @override
Future<int> findFreePort({bool ipv6 = false}) async => 12345; Future<int> findFreePort({bool ipv6 = false}) async => 12345;
} }
class FakeStopwatch implements Stopwatch {
@override
bool get isRunning => _isRunning;
bool _isRunning = false;
@override
void start() => _isRunning = true;
@override
void stop() => _isRunning = false;
@override
Duration elapsed = Duration.zero;
@override
int get elapsedMicroseconds => elapsed.inMicroseconds;
@override
int get elapsedMilliseconds => elapsed.inMilliseconds;
@override
int get elapsedTicks => elapsed.inMilliseconds;
@override
int get frequency => 1000;
@override
void reset() {
_isRunning = false;
elapsed = Duration.zero;
}
@override
String toString() => '$runtimeType $elapsed $isRunning';
}
class FakeStopwatchFactory implements StopwatchFactory {
FakeStopwatchFactory({
Stopwatch? stopwatch,
Map<String, Stopwatch>? stopwatches
}) : stopwatches = <String, Stopwatch>{
if (stopwatches != null) ...stopwatches,
if (stopwatch != null) '': stopwatch,
};
Map<String, Stopwatch> stopwatches;
@override
Stopwatch createStopwatch([String name = '']) {
return stopwatches[name] ?? FakeStopwatch();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment