Unverified Commit 52bd2ccb authored by Alexander Aprelev's avatar Alexander Aprelev Committed by GitHub

Report devfs stats (#25586)

* Collect devfs stats for better analytics

* Fix fields initialization

* Fix lints
parent 50f9b883
......@@ -350,6 +350,32 @@ class _DevFSHttpWriter {
}
}
// Basic statistics for DevFS update operation.
class UpdateFSReport {
UpdateFSReport({bool success = false,
int invalidatedSourcesCount = 0, int syncedBytes = 0}) {
_success = success;
_invalidatedSourcesCount = invalidatedSourcesCount;
_syncedBytes = syncedBytes;
}
bool get success => _success;
int get invalidatedSourcesCount => _invalidatedSourcesCount;
int get syncedBytes => _syncedBytes;
void incorporateResults(UpdateFSReport report) {
if (!report._success) {
_success = false;
}
_invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes;
}
bool _success;
int _invalidatedSourcesCount;
int _syncedBytes;
}
class DevFS {
/// Create a [DevFS] named [fsName] for the local files in [rootDirectory].
DevFS(VMService serviceProtocol,
......@@ -422,7 +448,7 @@ class DevFS {
/// Updates files on the device.
///
/// Returns the number of bytes synced.
Future<int> update({
Future<UpdateFSReport> update({
@required String mainPath,
String target,
AssetBundle bundle,
......@@ -487,7 +513,7 @@ class DevFS {
}
// Update modified files
int numBytes = 0;
int syncedBytes = 0;
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
_entries.forEach((Uri deviceUri, DevFSContent content) {
String archivePath;
......@@ -498,7 +524,7 @@ class DevFS {
// files to incremental compiler next time user does hot reload.
if (content.isModified || ((bundleDirty || bundleFirstUpload) && archivePath != null)) {
dirtyEntries[deviceUri] = content;
numBytes += content.size;
syncedBytes += content.size;
if (archivePath != null && (!bundleFirstUpload || content.isModifiedAfter(firstBuildTime)))
assetPathsToEvict.add(archivePath);
}
......@@ -516,7 +542,7 @@ class DevFS {
if (content is DevFSFileContent) {
filesUris.add(uri);
invalidatedFiles.add(content.file.uri.toString());
numBytes -= content.size;
syncedBytes -= content.size;
}
}
}
......@@ -545,7 +571,7 @@ class DevFS {
if (!dirtyEntries.containsKey(entryUri)) {
final DevFSFileContent content = DevFSFileContent(fs.file(compiledBinary));
dirtyEntries[entryUri] = content;
numBytes += content.size;
syncedBytes += content.size;
}
}
}
......@@ -576,7 +602,8 @@ class DevFS {
}
printTrace('DevFS: Sync finished');
return numBytes;
return UpdateFSReport(success: true, syncedBytes: syncedBytes,
invalidatedSourcesCount: invalidatedFiles.length);
}
void _scanFile(Uri deviceUri, FileSystemEntity file) {
......
......@@ -368,7 +368,7 @@ class FlutterDevice {
return 0;
}
Future<bool> updateDevFS({
Future<UpdateFSReport> updateDevFS({
String mainPath,
String target,
AssetBundle bundle,
......@@ -384,9 +384,9 @@ class FlutterDevice {
'Syncing files to device ${device.name}...',
expectSlowOperation: true,
);
int bytes = 0;
UpdateFSReport report;
try {
bytes = await devFS.update(
report = await devFS.update(
mainPath: mainPath,
target: target,
bundle: bundle,
......@@ -403,11 +403,11 @@ class FlutterDevice {
);
} on DevFSException {
devFSStatus.cancel();
return false;
return UpdateFSReport(success: false);
}
devFSStatus.stop();
printTrace('Synced ${getSizeAsMB(bytes)}.');
return true;
printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.');
return report;
}
void updateReloadStatus(bool wasReloadSuccessful) {
......
......@@ -19,6 +19,7 @@ import 'build_info.dart';
import 'compile.dart';
import 'dart/dependencies.dart';
import 'dart/pub.dart';
import 'devfs.dart';
import 'device.dart';
import 'globals.dart';
import 'resident_runner.dart';
......@@ -193,12 +194,12 @@ class HotRunner extends ResidentRunner {
return 3;
}
final Stopwatch initialUpdateDevFSsTimer = Stopwatch()..start();
final bool devfsResult = await _updateDevFS(fullRestart: true);
final UpdateFSReport devfsResult = await _updateDevFS(fullRestart: true);
_addBenchmarkData(
'hotReloadInitialDevFSSyncMilliseconds',
initialUpdateDevFSsTimer.elapsed.inMilliseconds,
);
if (!devfsResult)
if (!devfsResult.success)
return 3;
await refreshViews();
......@@ -329,10 +330,10 @@ class HotRunner extends ResidentRunner {
return devFSUris;
}
Future<bool> _updateDevFS({ bool fullRestart = false }) async {
Future<UpdateFSReport> _updateDevFS({ bool fullRestart = false }) async {
if (!await _refreshDartDependencies()) {
// Did not update DevFS because of a Dart source error.
return false;
return UpdateFSReport(success: false);
}
final bool isFirstUpload = assetBundle.wasBuiltOnce() == false;
final bool rebuildBundle = assetBundle.needsBuild();
......@@ -340,12 +341,12 @@ class HotRunner extends ResidentRunner {
printTrace('Updating assets');
final int result = await assetBundle.build();
if (result != 0)
return false;
return UpdateFSReport(success: false);
}
final List<bool> results = <bool>[];
final UpdateFSReport results = UpdateFSReport(success: true);
for (FlutterDevice device in flutterDevices) {
results.add(await device.updateDevFS(
results.incorporateResults(await device.updateDevFS(
mainPath: mainPath,
target: target,
bundle: assetBundle,
......@@ -358,16 +359,15 @@ class HotRunner extends ResidentRunner {
pathToReload: getReloadPath(fullRestart: fullRestart),
));
}
// If there any failures reported, bail out.
if (results.any((bool result) => !result)) {
return false;
if (!results.success) {
return results;
}
if (!hotRunnerConfig.stableDartDependencies) {
// Clear the set after the sync so they are recomputed next time.
_dartDependencies = null;
}
return true;
return results;
}
Future<void> _evictDirtyAssets() {
......@@ -460,8 +460,8 @@ class HotRunner extends ResidentRunner {
final Stopwatch restartTimer = Stopwatch()..start();
// TODO(aam): Add generator reset logic once we switch to using incremental
// compiler for full application recompilation on restart.
final bool updatedDevFS = await _updateDevFS(fullRestart: true);
if (!updatedDevFS) {
final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true);
if (!updatedDevFS.success) {
for (FlutterDevice device in flutterDevices) {
if (device.generator != null)
device.generator.reject();
......@@ -618,11 +618,11 @@ class HotRunner extends ResidentRunner {
final Stopwatch reloadTimer = Stopwatch()..start();
final Stopwatch devFSTimer = Stopwatch()..start();
final bool updatedDevFS = await _updateDevFS();
final UpdateFSReport updatedDevFS = await _updateDevFS();
// Record time it took to synchronize to DevFS.
_addBenchmarkData('hotReloadDevFSSyncMilliseconds',
devFSTimer.elapsed.inMilliseconds);
if (!updatedDevFS)
if (!updatedDevFS.success)
return OperationResult(1, 'DevFS synchronization failed');
String reloadMessage;
final Stopwatch vmReloadTimer = Stopwatch()..start();
......
......@@ -119,7 +119,7 @@ void main() {
devFSOperations.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
int bytes = await devFS.update(
UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -130,7 +130,7 @@ void main() {
]);
expect(devFS.assetPathsToEvict, isEmpty);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -141,7 +141,8 @@ void main() {
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
......@@ -150,7 +151,7 @@ void main() {
final File file = fs.file(fs.path.join(basePath, filePath2));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -160,13 +161,14 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('modify existing file on local file system', () async {
int bytes = await devFS.update(
UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -176,12 +178,13 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
final File file = fs.file(fs.path.join(basePath, filePath));
// Set the last modified time to 5 seconds in the past.
updateFileModificationTime(file.path, DateTime.now(), -5);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -191,10 +194,11 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -204,11 +208,12 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
// Set the last modified time to 5 seconds in the past.
updateFileModificationTime(file.path, DateTime.now(), -5);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -218,10 +223,11 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -231,7 +237,8 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
......@@ -240,7 +247,7 @@ void main() {
testUsingContext('delete a file from the local file system', () async {
final File file = fs.file(fs.path.join(basePath, filePath));
await file.delete();
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -251,14 +258,15 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add new package', () async {
await _createPackage(fs, 'newpkg', 'anotherfile.txt');
int bytes = await devFS.update(
UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -268,9 +276,10 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
bytes = await devFS.update(
report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -280,7 +289,8 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill.track.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
......@@ -302,7 +312,7 @@ void main() {
.map<String>((File file) => canonicalizePath(file.path))
.toList());
}
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
fileFilter: fileFilter,
generator: residentCompiler,
......@@ -313,14 +323,15 @@ void main() {
'writeFile test lib/foo.txt.dill build/app.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add an asset bundle', () async {
assetBundle.entries['a.txt'] = DevFSStringContent('abc');
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
......@@ -334,14 +345,14 @@ void main() {
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['a.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 25);
expect(report.syncedBytes, 25);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add a file to the asset bundle - bundleDirty', () async {
assetBundle.entries['b.txt'] = DevFSStringContent('abcd');
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
......@@ -358,14 +369,14 @@ void main() {
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'a.txt', 'b.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 29);
expect(report.syncedBytes, 29);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add a file to the asset bundle', () async {
assetBundle.entries['c.txt'] = DevFSStringContent('12');
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
generator: residentCompiler,
......@@ -379,14 +390,14 @@ void main() {
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'c.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 24);
expect(report.syncedBytes, 24);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete a file from the asset bundle', () async {
assetBundle.entries.remove('c.txt');
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
generator: residentCompiler,
......@@ -399,14 +410,15 @@ void main() {
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['c.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete all files from the asset bundle', () async {
assetBundle.entries.clear();
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
......@@ -423,7 +435,8 @@ void main() {
'a.txt', 'b.txt'
]));
devFS.assetPathsToEvict.clear();
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
......@@ -466,7 +479,7 @@ void main() {
vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
final int bytes = await devFS.update(
final UpdateFSReport report = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
......@@ -476,7 +489,8 @@ void main() {
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
expect(report.syncedBytes, 22);
expect(report.success, true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
......
......@@ -112,7 +112,8 @@ void main() {
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
)).thenAnswer((Invocation _) => Future<int>.value(1000));
)).thenAnswer((Invocation _) => Future<UpdateFSReport>.value(
UpdateFSReport(success: true, syncedBytes: 1000, invalidatedSourcesCount: 1)));
when(mockDevFs.assetPathsToEvict).thenReturn(Set<String>());
when(mockDevFs.baseUri).thenReturn(Uri.file('test'));
......
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