Chromium Code Reviews| 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..1442b4dce8429aad8afa036d29d8fbde1b1b563f |
| --- /dev/null |
| +++ b/pkg/compiler/tool/track_memory.dart |
| @@ -0,0 +1,187 @@ |
| +// 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() async { |
| + _printHeader(); |
| + _showProgress(0, 0, 0, 0); |
| + try { |
| + socket = await WebSocket.connect('ws://localhost:8181/ws'); |
|
sra1
2015/10/05 17:04:58
TODO: you might need to change the port if another
Siggi Cherem (dart-lang)
2015/10/05 17:19:44
Done. Just made it an arg which defaults to 8181
|
| + socket.listen(_handleResponse); |
| + await _resumeMainIsolateIfPaused(); |
| + _streamListen('GC'); |
| + _streamListen('Isolate'); |
| + _streamListen('Debug'); |
| + } on SocketException catch (e) { |
| + // TODO(sigmund): add better error messages, maybe option to retry. |
| + print('\n${_RED}error${_NONE}: $e'); |
| + } |
| +} |
| + |
| +/// 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}); |
|
sra1
2015/10/05 17:04:58
Some strings are 'x' others "x".
I assume the inte
Siggi Cherem (dart-lang)
2015/10/05 17:19:44
Good point - yeah, I had started with ', then copi
|
| + 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"; |