Register memory info command on vmservice for Android devices (#45568)
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 22dbcd9..3278850 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -475,6 +475,8 @@ return true; } + AndroidApk _package; + @override Future<LaunchResult> startApp( AndroidApk package, { @@ -609,6 +611,7 @@ return LaunchResult.failed(); } + _package = package; if (!debuggingOptions.debuggingEnabled) { return LaunchResult.succeeded(); } @@ -648,6 +651,21 @@ } @override + Future<MemoryInfo> queryMemoryInfo() async { + final RunResult runResult = await processUtils.run(adbCommandForDevice(<String>[ + 'shell', + 'dumpsys', + 'meminfo', + _package.launchActivity, + '-d', + ])); + if (runResult.exitCode != 0) { + return const MemoryInfo.empty(); + } + return parseMeminfoDump(runResult.stdout); + } + + @override void clearLogs() { processUtils.runSync(adbCommandForDevice(<String>['logcat', '-c'])); } @@ -712,6 +730,104 @@ return properties; } +/// Process the dumpsys info formatted in a table-like structure. +/// +/// Currently this only pulls information from the "App Summary" subsection. +/// +/// Example output: +/// +/// Applications Memory Usage (in Kilobytes): +/// Uptime: 441088659 Realtime: 521464097 +/// +/// ** MEMINFO in pid 16141 [io.flutter.demo.gallery] ** +/// Pss Private Private SwapPss Heap Heap Heap +/// Total Dirty Clean Dirty Size Alloc Free +/// ------ ------ ------ ------ ------ ------ ------ +/// Native Heap 8648 8620 0 16 20480 12403 8076 +/// Dalvik Heap 547 424 40 18 2628 1092 1536 +/// Dalvik Other 464 464 0 0 +/// Stack 496 496 0 0 +/// Ashmem 2 0 0 0 +/// Gfx dev 212 204 0 0 +/// Other dev 48 0 48 0 +/// .so mmap 10770 708 9372 25 +/// .apk mmap 240 0 0 0 +/// .ttf mmap 35 0 32 0 +/// .dex mmap 2205 4 1172 0 +/// .oat mmap 64 0 0 0 +/// .art mmap 4228 3848 24 2 +/// Other mmap 20713 4 20704 0 +/// GL mtrack 2380 2380 0 0 +/// Unknown 43971 43968 0 1 +/// TOTAL 95085 61120 31392 62 23108 13495 9612 +/// +/// App Summary +/// Pss(KB) +/// ------ +/// Java Heap: 4296 +/// Native Heap: 8620 +/// Code: 11288 +/// Stack: 496 +/// Graphics: 2584 +/// Private Other: 65228 +/// System: 2573 +/// +/// TOTAL: 95085 TOTAL SWAP PSS: 62 +/// +/// Objects +/// Views: 9 ViewRootImpl: 1 +/// AppContexts: 3 Activities: 1 +/// Assets: 4 AssetManagers: 3 +/// Local Binders: 10 Proxy Binders: 18 +/// Parcel memory: 6 Parcel count: 24 +/// Death Recipients: 0 OpenSSL Sockets: 0 +/// WebViews: 0 +/// +/// SQL +/// MEMORY_USED: 0 +/// PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0 +/// ... +/// +/// For more information, see https://developer.android.com/studio/command-line/dumpsys. +@visibleForTesting +AndroidMemoryInfo parseMeminfoDump(String input) { + final AndroidMemoryInfo androidMemoryInfo = AndroidMemoryInfo(); + input + .split('\n') + .skipWhile((String line) => !line.contains('App Summary')) + .takeWhile((String line) => !line.contains('TOTAL')) + .where((String line) => line.contains(':')) + .forEach((String line) { + final List<String> sections = line.trim().split(':'); + final String key = sections.first.trim(); + final int value = int.tryParse(sections.last.trim()) ?? 0; + switch (key) { + case AndroidMemoryInfo._kJavaHeapKey: + androidMemoryInfo.javaHeap = value; + break; + case AndroidMemoryInfo._kNativeHeapKey: + androidMemoryInfo.nativeHeap = value; + break; + case AndroidMemoryInfo._kCodeKey: + androidMemoryInfo.code = value; + break; + case AndroidMemoryInfo._kStackKey: + androidMemoryInfo.stack = value; + break; + case AndroidMemoryInfo._kGraphicsKey: + androidMemoryInfo.graphics = value; + break; + case AndroidMemoryInfo._kPrivateOtherKey: + androidMemoryInfo.privateOther = value; + break; + case AndroidMemoryInfo._kSystemKey: + androidMemoryInfo.system = value; + break; + } + }); + return androidMemoryInfo; +} + /// Return the list of connected ADB devices. List<AndroidDevice> getAdbDevices() { final String adbPath = getAdbPath(androidSdk); @@ -736,6 +852,42 @@ return devices; } +/// Android specific implementation of memory info. +class AndroidMemoryInfo extends MemoryInfo { + static const String _kJavaHeapKey = 'Java Heap'; + static const String _kNativeHeapKey = 'Native Heap'; + static const String _kCodeKey = 'Code'; + static const String _kStackKey = 'Stack'; + static const String _kGraphicsKey = 'Graphics'; + static const String _kPrivateOtherKey = 'Private Other'; + static const String _kSystemKey = 'System'; + static const String _kTotalKey = 'Total'; + + // Each measurement has KB as a unit. + int javaHeap = 0; + int nativeHeap = 0; + int code = 0; + int stack = 0; + int graphics = 0; + int privateOther = 0; + int system = 0; + + @override + Map<String, Object> toJson() { + return <String, Object>{ + 'platform': 'Android', + _kJavaHeapKey: javaHeap, + _kNativeHeapKey: nativeHeap, + _kCodeKey: code, + _kStackKey: stack, + _kGraphicsKey: graphics, + _kPrivateOtherKey: privateOther, + _kSystemKey: system, + _kTotalKey: javaHeap + nativeHeap + code + stack + graphics + privateOther + system, + }; + } +} + /// Get diagnostics about issues with any connected devices. Future<List<String>> getAdbDeviceDiagnostics() async { final String adbPath = getAdbPath(androidSdk);
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 46b65a5..3a539b8 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -423,6 +423,14 @@ /// Stop an app package on the current device. Future<bool> stopApp(covariant ApplicationPackage app); + /// Query the current application memory usage.. + /// + /// If the device does not support this callback, an empty map + /// is returned. + Future<MemoryInfo> queryMemoryInfo() { + return Future<MemoryInfo>.value(const MemoryInfo.empty()); + } + Future<void> takeScreenshot(File outputFile) => Future<void>.error('unimplemented'); @override @@ -487,6 +495,25 @@ void dispose() {} } +/// Information about an application's memory usage. +abstract class MemoryInfo { + /// Const constructor to allow subclasses to be const. + const MemoryInfo(); + + /// Create a [MemoryInfo] object with no information. + const factory MemoryInfo.empty() = _NoMemoryInfo; + + /// Convert the object to a JSON representation suitable for serialization. + Map<String, Object> toJson(); +} + +class _NoMemoryInfo implements MemoryInfo { + const _NoMemoryInfo(); + + @override + Map<String, Object> toJson() => <String, Object>{}; +} + class DebuggingOptions { DebuggingOptions.enabled( this.buildInfo, {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index f8d6faf..0457172 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -177,6 +177,7 @@ reloadSources: reloadSources, restart: restart, compileExpression: compileExpression, + device: device, ); } on Exception catch (exception) { printTrace('Fail to connect to service protocol: $observatoryUri: $exception');
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 58a3931..657b719 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -19,6 +19,7 @@ import 'base/io.dart' as io; import 'base/utils.dart'; import 'convert.dart' show base64, utf8; +import 'device.dart'; import 'globals.dart'; import 'version.dart'; import 'vmservice_record_replay.dart'; @@ -101,7 +102,13 @@ /// Override `VMServiceConnector` in [context] to return a different VMService /// from [VMService.connect] (used by tests). -typedef VMServiceConnector = Future<VMService> Function(Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, io.CompressionOptions compression }); +typedef VMServiceConnector = Future<VMService> Function(Uri httpUri, { + ReloadSources reloadSources, + Restart restart, + CompileExpression compileExpression, + io.CompressionOptions compression, + Device device, +}); /// A connection to the Dart VM Service. // TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031 @@ -113,6 +120,7 @@ ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, + Device device, ) { _vm = VM._empty(this); _peer.listen().catchError(_connectionError.completeError); @@ -264,6 +272,16 @@ 'alias': 'Flutter Tools', }); } + if (device != null) { + _peer.registerMethod('flutterMemoryInfo', (rpc.Parameters params) async { + final MemoryInfo result = await device.queryMemoryInfo(); + return result.toJson(); + }); + _peer.sendNotification('registerService', <String, String>{ + 'service': 'flutterMemoryInfo', + 'alias': 'Flutter Tools', + }); + } } /// Enables recording of VMService JSON-rpc activity to the specified base @@ -310,9 +328,16 @@ Restart restart, CompileExpression compileExpression, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, + Device device, }) async { final VMServiceConnector connector = context.get<VMServiceConnector>() ?? VMService._connect; - return connector(httpUri, reloadSources: reloadSources, restart: restart, compileExpression: compileExpression, compression: compression); + return connector(httpUri, + reloadSources: reloadSources, + restart: restart, + compileExpression: compileExpression, + compression: compression, + device: device, + ); } static Future<VMService> _connect( @@ -321,11 +346,12 @@ Restart restart, CompileExpression compileExpression, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, + Device device, }) async { final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws')); final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression); final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError); - final VMService service = VMService(peer, httpUri, wsUri, reloadSources, restart, compileExpression); + final VMService service = VMService(peer, httpUri, wsUri, reloadSources, restart, compileExpression, device); // This call is to ensure we are able to establish a connection instead of // keeping on trucking and failing farther down the process. await service._sendRequest('getVersion', const <String, dynamic>{});