| Index: pkg/compiler/tool/track_memory.dart
|
| diff --git a/pkg/compiler/tool/track_memory.dart b/pkg/compiler/tool/track_memory.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..effd92f000fb3b93fad985d8be7892f6a96b9d8d
|
| --- /dev/null
|
| +++ b/pkg/compiler/tool/track_memory.dart
|
| @@ -0,0 +1,193 @@
|
| +// Copyright (c) 2015, 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.
|
| +
|
| +/// A script to track the high water-mark of memory usage of an application.
|
| +/// To monitor how much memory dart2js is using, run dart2js as follows:
|
| +///
|
| +/// DART_VM_OPTIONS=--observe dart2js ...
|
| +///
|
| +/// and run this script immediately after.
|
| +library compiler.tool.track_memory;
|
| +
|
| +import 'dart:math' show max;
|
| +import 'dart:io';
|
| +import 'dart:async';
|
| +
|
| +import 'dart:convert';
|
| +
|
| +/// Socket to connect to the vm observatory service.
|
| +WebSocket socket;
|
| +
|
| +main(args) async {
|
| + _printHeader();
|
| + _showProgress(0, 0, 0, 0);
|
| + try {
|
| + var port = args.length > 0 ? int.parse(args[0]) : 8181;
|
| + socket = await WebSocket.connect('ws://localhost:$port/ws');
|
| + socket.listen(_handleResponse);
|
| + await _resumeMainIsolateIfPaused();
|
| + _streamListen('GC');
|
| + _streamListen('Isolate');
|
| + _streamListen('Debug');
|
| + } catch (e) {
|
| + // TODO(sigmund): add better error messages, maybe option to retry.
|
| + print('\n${_RED}error${_NONE}: $e');
|
| + print('usage:\n'
|
| + ' Start a Dart process with the --observe flag (and optionally '
|
| + 'the --pause_isolates_on_start flag), then invoke:\n'
|
| + ' dart tool/track_memory.dart [<port>]\n'
|
| + ' by default port is 8181');
|
| + }
|
| +}
|
| +
|
| +/// Internal counter for request ids.
|
| +int _requestId = 0;
|
| +Map _pendingResponses = {};
|
| +
|
| +/// Subscribe to listen to a vm service data stream.
|
| +_streamListen(String streamId) =>
|
| + _sendMessage('streamListen', {'streamId': '$streamId'});
|
| +
|
| +/// Tell the vm service to resume a specific isolate.
|
| +_resumeIsolate(String isolateId) =>
|
| + _sendMessage('resume', {'isolateId': '$isolateId'});
|
| +
|
| +/// Resumes the main isolate if it was paused on start.
|
| +_resumeMainIsolateIfPaused() async {
|
| + var vm = await _sendMessage('getVM');
|
| + var isolateId = vm['isolates'][0]['id'];
|
| + var isolate = await _sendMessage('getIsolate', {'isolateId': isolateId});
|
| + bool isPaused = isolate['pauseEvent']['kind'] == 'PauseStart';
|
| + if (isPaused) _resumeIsolate(isolateId);
|
| +}
|
| +
|
| +/// Send a message to the vm service.
|
| +Future _sendMessage(String method, [Map args= const {}]) {
|
| + var id = _requestId++;
|
| + _pendingResponses[id] = new Completer();
|
| + socket.add(JSON.encode({
|
| + 'jsonrpc': '2.0',
|
| + 'id': '$id',
|
| + 'method': '$method',
|
| + 'params': args,
|
| + }));
|
| + return _pendingResponses[id].future;
|
| +}
|
| +
|
| +/// Handle all responses
|
| +_handleResponse(String s) {
|
| + var json = JSON.decode(s);
|
| + if (json['method'] != 'streamNotify') {
|
| + var id = json['id'];
|
| + if (id is String) id = int.parse(id);
|
| + if (id == null || !_pendingResponses.containsKey(id)) return;
|
| + _pendingResponses.remove(id).complete(json['result']);
|
| + return;
|
| + }
|
| +
|
| + // isolate pauses on exit automatically. We detect this to stop and exit.
|
| + if (json['params']['streamId'] == 'Debug') {
|
| + _handleDebug(json);
|
| + } else if (json['params']['streamId'] == 'Isolate') {
|
| + _handleIsolate(json);
|
| + } else if (json['params']['streamId'] == 'GC') {
|
| + _handleGC(json);
|
| + }
|
| +}
|
| +
|
| +/// Handle a `Debug` notification.
|
| +_handleDebug(Map json) {
|
| + var isolateId = json['params']['event']['isolate']['id'];
|
| + if (json['params']['event']['kind'] == 'PauseStart') {
|
| + _resumeIsolate(isolateId);
|
| + } if (json['params']['event']['kind'] == 'PauseExit') {
|
| + _resumeIsolate(isolateId);
|
| + }
|
| +}
|
| +
|
| +/// Handle a `Isolate` notification.
|
| +_handleIsolate(Map json) {
|
| + if (json['params']['event']['kind'] == 'IsolateExit') {
|
| + print('');
|
| + socket.close();
|
| + }
|
| +}
|
| +
|
| +/// Handle a `GC` notification.
|
| +_handleGC(Map json) {
|
| + // print(new JsonEncoder.withIndent(' ').convert(json));
|
| + var event = json['params']['event'];
|
| + var newUsed = event['new']['used'];
|
| + var newCapacity = event['new']['capacity'];
|
| + var oldUsed = event['old']['used'];
|
| + var oldCapacity = event['old']['capacity'];
|
| + _showProgress(newUsed, newCapacity, oldUsed, oldCapacity);
|
| +}
|
| +
|
| +int lastNewUsed = 0;
|
| +int lastOldUsed = 0;
|
| +int lastMaxUsed = 0;
|
| +int lastNewCapacity = 0;
|
| +int lastOldCapacity = 0;
|
| +int lastMaxCapacity = 0;
|
| +
|
| +/// Shows a status line with use/capacity numbers for new/old/total/max,
|
| +/// highlighting in red when capacity increases, and in green when it decreases.
|
| +_showProgress(newUsed, newCapacity, oldUsed, oldCapacity) {
|
| + var sb = new StringBuffer();
|
| + sb.write('\r '); // replace the status-line in place
|
| + _writeNumber(sb, lastNewUsed, newUsed);
|
| + _writeNumber(sb, lastNewCapacity, newCapacity, color: true);
|
| +
|
| + sb.write(' | ');
|
| + _writeNumber(sb, lastOldUsed, oldUsed);
|
| + _writeNumber(sb, lastOldCapacity, oldCapacity, color: true);
|
| +
|
| + sb.write(' | ');
|
| + _writeNumber(sb, lastNewUsed + lastOldUsed, newUsed + oldUsed);
|
| + _writeNumber(sb, lastNewCapacity + lastOldCapacity, newCapacity + oldCapacity,
|
| + color: true);
|
| +
|
| + sb.write(' | ');
|
| + var maxUsed = max(lastMaxUsed, newUsed + oldUsed);
|
| + var maxCapacity = max(lastMaxCapacity, newCapacity + oldCapacity);
|
| + _writeNumber(sb, lastMaxUsed, maxUsed);
|
| + _writeNumber(sb, lastMaxCapacity, maxCapacity, color: true);
|
| + stdout.write('$sb');
|
| +
|
| + lastNewUsed = newUsed;
|
| + lastOldUsed = oldUsed;
|
| + lastMaxUsed = maxUsed;
|
| + lastNewCapacity = newCapacity;
|
| + lastOldCapacity = oldCapacity;
|
| + lastMaxCapacity = maxCapacity;
|
| +}
|
| +
|
| +const mega = 1024 * 1024;
|
| +_writeNumber(sb, before, now, {color: false}) {
|
| + if (color) sb.write(before < now ? _RED : before > now ? _GREEN : '');
|
| + var string;
|
| + if (now < 1024) {
|
| + string = ' ${now}b';
|
| + } else if (now < mega) {
|
| + string = ' ${(now/1024).toStringAsFixed(0)}K';
|
| + } else {
|
| + string = ' ${(now/mega).toStringAsFixed(1)}M';
|
| + }
|
| + if (string.length < 10) string = '${' ' * (8 - string.length)}$string';
|
| + sb.write(string);
|
| + if (color) sb.write(before != now ? _NONE : '');
|
| + return before > now;
|
| +}
|
| +
|
| +_printHeader() {
|
| + print('''
|
| +Memory usage:
|
| + new generation | old generation | total | max
|
| + in-use/capacity | in-use/capacity | in-use/capacity | in-use/capacity ''');
|
| +}
|
| +
|
| +const _RED = '\x1b[31m';
|
| +const _GREEN = '\x1b[32m';
|
| +const _NONE = '\x1b[0m';
|
|
|