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'; |