From 959f0a299c6f22b40fcb1a849eaacfa5a027dc5c Mon Sep 17 00:00:00 2001 From: Jens Johansen Date: Wed, 14 Dec 2022 18:11:25 +0000 Subject: [PATCH] [analyzer] Report memory usage on Windows too Change-Id: I6a31370c51e40a9593f8875d365a43d9f4111aaf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/275660 Reviewed-by: Brian Wilkerson Reviewed-by: Konstantin Shcheglov Commit-Queue: Jens Johansen --- .../lib/src/status/diagnostics.dart | 6 +- .../lib/src/utilities/profiling.dart | 55 ++++++++++++++++++- .../test/src/utilities/profiling_test.dart | 28 ++++++++-- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/pkg/analysis_server/lib/src/status/diagnostics.dart b/pkg/analysis_server/lib/src/status/diagnostics.dart index aaed440aae0..a7778c29e52 100644 --- a/pkg/analysis_server/lib/src/status/diagnostics.dart +++ b/pkg/analysis_server/lib/src/status/diagnostics.dart @@ -1065,8 +1065,10 @@ class MemoryAndCpuPage extends DiagnosticPageWithNav { var serviceProtocolInfo = await developer.Service.getInfo(); if (usage != null) { - buf.writeln( - writeOption('CPU', printPercentage(usage.cpuPercentage / 100.0))); + var cpuPercentage = usage.cpuPercentage; + if (cpuPercentage != null) { + buf.writeln(writeOption('CPU', printPercentage(cpuPercentage / 100.0))); + } buf.writeln(writeOption('Memory', '${usage.memoryMB.round()} MB')); h3('VM'); diff --git a/pkg/analysis_server/lib/src/utilities/profiling.dart b/pkg/analysis_server/lib/src/utilities/profiling.dart index f1fcddb969e..b6b1fec3f6f 100644 --- a/pkg/analysis_server/lib/src/utilities/profiling.dart +++ b/pkg/analysis_server/lib/src/utilities/profiling.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:convert'; import 'dart:io'; /// A class that can return memory and cpu usage information for a given @@ -19,6 +20,10 @@ abstract class ProcessProfiler { return _PosixProcessProfiler(); } + if (Platform.isWindows) { + return _WindowsProcessProfiler(); + } + // Not a supported platform. return null; } @@ -27,7 +32,7 @@ abstract class ProcessProfiler { class UsageInfo { /// A number between 0.0 and 100.0 * the number of host CPUs (but typically /// never more than slightly above 100.0). - final double cpuPercentage; + final double? cpuPercentage; /// The process memory usage in kilobytes. final int memoryKB; @@ -37,7 +42,12 @@ class UsageInfo { double get memoryMB => memoryKB / 1024; @override - String toString() => '$cpuPercentage% ${memoryMB.toStringAsFixed(1)}MB'; + String toString() { + if (cpuPercentage != null) { + return '$cpuPercentage% ${memoryMB.toStringAsFixed(1)}MB'; + } + return '${memoryMB.toStringAsFixed(1)}MB'; + } } class _PosixProcessProfiler extends ProcessProfiler { @@ -74,3 +84,44 @@ class _PosixProcessProfiler extends ProcessProfiler { } } } + +class _WindowsProcessProfiler extends ProcessProfiler { + _WindowsProcessProfiler() : super._(); + + @override + Future getProcessUsage(int processId) async { + try { + var result = await Process.run( + 'tasklist', ['/FI', 'PID eq $processId', '/NH', '/FO', 'csv']); + + if (result.exitCode != 0) { + return Future.value(null); + } + + return Future.value(_parse(result.stdout as String)); + } catch (e) { + return Future.error(e); + } + } + + UsageInfo? _parse(String tasklistResults) { + try { + var lines = tasklistResults.split(RegExp("\r?\n")); + for (var line in lines) { + if (line.trim().isEmpty) continue; + // Hacky parsing of csv line. + var entries = jsonDecode("[$line]") as List; + if (entries.length != 5) continue; + // E.g. 123,456 K + var memory = entries[4] as String; + memory = memory.substring(0, memory.indexOf(" ")); + memory = memory.replaceAll(",", ""); + memory = memory.replaceAll(".", ""); + return UsageInfo(null, int.parse(memory)); + } + return null; + } catch (e) { + return null; + } + } +} diff --git a/pkg/analysis_server/test/src/utilities/profiling_test.dart b/pkg/analysis_server/test/src/utilities/profiling_test.dart index de9f186d2ab..a1b9a07f5c0 100644 --- a/pkg/analysis_server/test/src/utilities/profiling_test.dart +++ b/pkg/analysis_server/test/src/utilities/profiling_test.dart @@ -3,17 +3,13 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:io'; +import 'dart:typed_data'; import 'package:analysis_server/src/utilities/profiling.dart'; import 'package:test/test.dart'; void main() { group('ProcessProfiler', () { - // Skip on windows. - if (Platform.isWindows) { - return; - } - test('getProfilerForPlatform', () async { expect(ProcessProfiler.getProfilerForPlatform(), isNotNull); }); @@ -22,8 +18,28 @@ void main() { var profiler = ProcessProfiler.getProfilerForPlatform()!; var info = (await profiler.getProcessUsage(pid))!; - expect(info.cpuPercentage, greaterThanOrEqualTo(0.0)); + print("Uses ${info.memoryKB} KB"); + + if (Platform.isWindows) { + expect(info.cpuPercentage, isNull); + } else { + expect(info.cpuPercentage, greaterThanOrEqualTo(0.0)); + } expect(info.memoryKB, greaterThanOrEqualTo(0)); + + // Use ~50 MB more memory and ensure that we actually use more memory + // as reported by the return value. + Uint8List use50mb = Uint8List(50 * 1024 * 1024); + for (int i = 0; i < use50mb.length; i++) { + use50mb[i] = i % 200; + } + var info2 = (await profiler.getProcessUsage(pid))!; + print("Now uses ${info2.memoryKB} KB"); + + for (var b in use50mb) { + if (b < 0) throw "This shouldn't happen, but we're using the data!"; + } + expect(info2.memoryKB, greaterThan(info.memoryKB)); }); }); }