OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// A script to track the high water-mark of memory usage of an application. |
| 6 /// To monitor how much memory dart2js is using, run dart2js as follows: |
| 7 /// |
| 8 /// DART_VM_OPTIONS=--observe dart2js ... |
| 9 /// |
| 10 /// and run this script immediately after. |
| 11 library compiler.tool.track_memory; |
| 12 |
| 13 import 'dart:math' show max; |
| 14 import 'dart:io'; |
| 15 import 'dart:async'; |
| 16 |
| 17 import 'dart:convert'; |
| 18 |
| 19 /// Socket to connect to the vm observatory service. |
| 20 WebSocket socket; |
| 21 |
| 22 main(args) async { |
| 23 _printHeader(); |
| 24 _showProgress(0, 0, 0, 0); |
| 25 try { |
| 26 var port = args.length > 0 ? int.parse(args[0]) : 8181; |
| 27 socket = await WebSocket.connect('ws://localhost:$port/ws'); |
| 28 socket.listen(_handleResponse); |
| 29 await _resumeMainIsolateIfPaused(); |
| 30 _streamListen('GC'); |
| 31 _streamListen('Isolate'); |
| 32 _streamListen('Debug'); |
| 33 } catch (e) { |
| 34 // TODO(sigmund): add better error messages, maybe option to retry. |
| 35 print('\n${_RED}error${_NONE}: $e'); |
| 36 print('usage:\n' |
| 37 ' Start a Dart process with the --observe flag (and optionally ' |
| 38 'the --pause_isolates_on_start flag), then invoke:\n' |
| 39 ' dart tool/track_memory.dart [<port>]\n' |
| 40 ' by default port is 8181'); |
| 41 } |
| 42 } |
| 43 |
| 44 /// Internal counter for request ids. |
| 45 int _requestId = 0; |
| 46 Map _pendingResponses = {}; |
| 47 |
| 48 /// Subscribe to listen to a vm service data stream. |
| 49 _streamListen(String streamId) => |
| 50 _sendMessage('streamListen', {'streamId': '$streamId'}); |
| 51 |
| 52 /// Tell the vm service to resume a specific isolate. |
| 53 _resumeIsolate(String isolateId) => |
| 54 _sendMessage('resume', {'isolateId': '$isolateId'}); |
| 55 |
| 56 /// Resumes the main isolate if it was paused on start. |
| 57 _resumeMainIsolateIfPaused() async { |
| 58 var vm = await _sendMessage('getVM'); |
| 59 var isolateId = vm['isolates'][0]['id']; |
| 60 var isolate = await _sendMessage('getIsolate', {'isolateId': isolateId}); |
| 61 bool isPaused = isolate['pauseEvent']['kind'] == 'PauseStart'; |
| 62 if (isPaused) _resumeIsolate(isolateId); |
| 63 } |
| 64 |
| 65 /// Send a message to the vm service. |
| 66 Future _sendMessage(String method, [Map args= const {}]) { |
| 67 var id = _requestId++; |
| 68 _pendingResponses[id] = new Completer(); |
| 69 socket.add(JSON.encode({ |
| 70 'jsonrpc': '2.0', |
| 71 'id': '$id', |
| 72 'method': '$method', |
| 73 'params': args, |
| 74 })); |
| 75 return _pendingResponses[id].future; |
| 76 } |
| 77 |
| 78 /// Handle all responses |
| 79 _handleResponse(String s) { |
| 80 var json = JSON.decode(s); |
| 81 if (json['method'] != 'streamNotify') { |
| 82 var id = json['id']; |
| 83 if (id is String) id = int.parse(id); |
| 84 if (id == null || !_pendingResponses.containsKey(id)) return; |
| 85 _pendingResponses.remove(id).complete(json['result']); |
| 86 return; |
| 87 } |
| 88 |
| 89 // isolate pauses on exit automatically. We detect this to stop and exit. |
| 90 if (json['params']['streamId'] == 'Debug') { |
| 91 _handleDebug(json); |
| 92 } else if (json['params']['streamId'] == 'Isolate') { |
| 93 _handleIsolate(json); |
| 94 } else if (json['params']['streamId'] == 'GC') { |
| 95 _handleGC(json); |
| 96 } |
| 97 } |
| 98 |
| 99 /// Handle a `Debug` notification. |
| 100 _handleDebug(Map json) { |
| 101 var isolateId = json['params']['event']['isolate']['id']; |
| 102 if (json['params']['event']['kind'] == 'PauseStart') { |
| 103 _resumeIsolate(isolateId); |
| 104 } if (json['params']['event']['kind'] == 'PauseExit') { |
| 105 _resumeIsolate(isolateId); |
| 106 } |
| 107 } |
| 108 |
| 109 /// Handle a `Isolate` notification. |
| 110 _handleIsolate(Map json) { |
| 111 if (json['params']['event']['kind'] == 'IsolateExit') { |
| 112 print(''); |
| 113 socket.close(); |
| 114 } |
| 115 } |
| 116 |
| 117 /// Handle a `GC` notification. |
| 118 _handleGC(Map json) { |
| 119 // print(new JsonEncoder.withIndent(' ').convert(json)); |
| 120 var event = json['params']['event']; |
| 121 var newUsed = event['new']['used']; |
| 122 var newCapacity = event['new']['capacity']; |
| 123 var oldUsed = event['old']['used']; |
| 124 var oldCapacity = event['old']['capacity']; |
| 125 _showProgress(newUsed, newCapacity, oldUsed, oldCapacity); |
| 126 } |
| 127 |
| 128 int lastNewUsed = 0; |
| 129 int lastOldUsed = 0; |
| 130 int lastMaxUsed = 0; |
| 131 int lastNewCapacity = 0; |
| 132 int lastOldCapacity = 0; |
| 133 int lastMaxCapacity = 0; |
| 134 |
| 135 /// Shows a status line with use/capacity numbers for new/old/total/max, |
| 136 /// highlighting in red when capacity increases, and in green when it decreases. |
| 137 _showProgress(newUsed, newCapacity, oldUsed, oldCapacity) { |
| 138 var sb = new StringBuffer(); |
| 139 sb.write('\r '); // replace the status-line in place |
| 140 _writeNumber(sb, lastNewUsed, newUsed); |
| 141 _writeNumber(sb, lastNewCapacity, newCapacity, color: true); |
| 142 |
| 143 sb.write(' | '); |
| 144 _writeNumber(sb, lastOldUsed, oldUsed); |
| 145 _writeNumber(sb, lastOldCapacity, oldCapacity, color: true); |
| 146 |
| 147 sb.write(' | '); |
| 148 _writeNumber(sb, lastNewUsed + lastOldUsed, newUsed + oldUsed); |
| 149 _writeNumber(sb, lastNewCapacity + lastOldCapacity, newCapacity + oldCapacity, |
| 150 color: true); |
| 151 |
| 152 sb.write(' | '); |
| 153 var maxUsed = max(lastMaxUsed, newUsed + oldUsed); |
| 154 var maxCapacity = max(lastMaxCapacity, newCapacity + oldCapacity); |
| 155 _writeNumber(sb, lastMaxUsed, maxUsed); |
| 156 _writeNumber(sb, lastMaxCapacity, maxCapacity, color: true); |
| 157 stdout.write('$sb'); |
| 158 |
| 159 lastNewUsed = newUsed; |
| 160 lastOldUsed = oldUsed; |
| 161 lastMaxUsed = maxUsed; |
| 162 lastNewCapacity = newCapacity; |
| 163 lastOldCapacity = oldCapacity; |
| 164 lastMaxCapacity = maxCapacity; |
| 165 } |
| 166 |
| 167 const mega = 1024 * 1024; |
| 168 _writeNumber(sb, before, now, {color: false}) { |
| 169 if (color) sb.write(before < now ? _RED : before > now ? _GREEN : ''); |
| 170 var string; |
| 171 if (now < 1024) { |
| 172 string = ' ${now}b'; |
| 173 } else if (now < mega) { |
| 174 string = ' ${(now/1024).toStringAsFixed(0)}K'; |
| 175 } else { |
| 176 string = ' ${(now/mega).toStringAsFixed(1)}M'; |
| 177 } |
| 178 if (string.length < 10) string = '${' ' * (8 - string.length)}$string'; |
| 179 sb.write(string); |
| 180 if (color) sb.write(before != now ? _NONE : ''); |
| 181 return before > now; |
| 182 } |
| 183 |
| 184 _printHeader() { |
| 185 print(''' |
| 186 Memory usage: |
| 187 new generation | old generation | total | max |
| 188 in-use/capacity | in-use/capacity | in-use/capacity | in-use/capacity '''); |
| 189 } |
| 190 |
| 191 const _RED = '\x1b[31m'; |
| 192 const _GREEN = '\x1b[32m'; |
| 193 const _NONE = '\x1b[0m'; |
OLD | NEW |