Report devfs stats (#25586)

* Collect devfs stats for better analytics

* Fix fields initialization

* Fix lints
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index b7c78cb..07adbba 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -350,6 +350,32 @@
   }
 }
 
+// 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 @@
   /// 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 @@
     }
 
     // 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 @@
       // 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 @@
         if (content is DevFSFileContent) {
           filesUris.add(uri);
           invalidatedFiles.add(content.file.uri.toString());
-          numBytes -= content.size;
+          syncedBytes -= content.size;
         }
       }
     }
@@ -545,7 +571,7 @@
         if (!dirtyEntries.containsKey(entryUri)) {
           final DevFSFileContent content = DevFSFileContent(fs.file(compiledBinary));
           dirtyEntries[entryUri] = content;
-          numBytes += content.size;
+          syncedBytes += content.size;
         }
       }
     }
@@ -576,7 +602,8 @@
     }
 
     printTrace('DevFS: Sync finished');
-    return numBytes;
+    return UpdateFSReport(success: true, syncedBytes: syncedBytes,
+        invalidatedSourcesCount: invalidatedFiles.length);
   }
 
   void _scanFile(Uri deviceUri, FileSystemEntity file) {