| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | |
| // 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'; | |
| import 'package:analyzer/src/util/platform_info.dart'; | |
| /// A class that can return memory and cpu usage information for a given | |
| /// process. | |
| abstract class ProcessProfiler { | |
| new _(); | |
| Future<UsageInfo?> getProcessUsage(int processId); | |
| /// Return a [ProcessProfiler] instance suitable for the current host | |
| /// platform. This can return `null` if we're not able to gather memory and | |
| /// cpu information for the current platform. | |
| static ProcessProfiler? getProfilerForPlatform() { | |
| if (platform.isLinux || platform.isMacOS) { | |
| return _PosixProcessProfiler(); | |
| } | |
| if (platform.isWindows) { | |
| return _WindowsProcessProfiler(); | |
| } | |
| // Not a supported platform. | |
| return null; | |
| } | |
| } | |
| 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; | |
| /// The process memory usage in kilobytes. | |
| final int memoryKB; | |
| new(this.cpuPercentage, this.memoryKB); | |
| double get memoryMB => memoryKB / 1024; | |
| @override | |
| String toString() { | |
| if (cpuPercentage != null) { | |
| return '$cpuPercentage% ${memoryMB.toStringAsFixed(1)}MB'; | |
| } | |
| return '${memoryMB.toStringAsFixed(1)}MB'; | |
| } | |
| } | |
| class _PosixProcessProfiler extends ProcessProfiler { | |
| static final RegExp stringSplitRegExp = RegExp(r'\s+'); | |
| new() : super._(); | |
| @override | |
| Future<UsageInfo?> getProcessUsage(int processId) { | |
| try { | |
| // Execution time is typically 2-4ms. | |
| var future = Process.run('ps', [ | |
| '-o', | |
| '%cpu=,rss=', | |
| processId.toString(), | |
| ]); | |
| return future.then((ProcessResult result) { | |
| if (result.exitCode != 0) { | |
| return Future.value(); | |
| } | |
| return Future.value(_parse(result.stdout as String)); | |
| }); | |
| } catch (e) { | |
| return Future.error(e); | |
| } | |
| } | |
| UsageInfo? _parse(String psResults) { | |
| try { | |
| // " 0.0 378940" | |
| var line = psResults.split('\n').first.trim(); | |
| var values = line.split(stringSplitRegExp); | |
| return UsageInfo(double.parse(values[0]), int.parse(values[1])); | |
| } catch (e) { | |
| return null; | |
| } | |
| } | |
| } | |
| class _WindowsProcessProfiler extends ProcessProfiler { | |
| new() : super._(); | |
| @override | |
| Future<UsageInfo?> getProcessUsage(int processId) async { | |
| try { | |
| var result = await Process.run('tasklist', [ | |
| '/FI', | |
| 'PID eq $processId', | |
| '/NH', | |
| '/FO', | |
| 'csv', | |
| ]); | |
| if (result.exitCode != 0) { | |
| return await Future.value(); | |
| } | |
| return await 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; | |
| } | |
| } | |
| } |