From 66db8a7f0ec197c742b182b674ca2784e57765b8 Mon Sep 17 00:00:00 2001 From: Sigmund Cherem Date: Mon, 5 Oct 2015 10:20:36 -0700 Subject: [PATCH] add tool to track memory watermark R=sra@google.com Review URL: https://codereview.chromium.org/1384833002 . --- pkg/compiler/tool/track_memory.dart | 193 ++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 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 00000000000..effd92f000f --- /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 []\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';