blob: fdb3981d3abbfd6f098986e835452f0c16aae1a6 [file] [log] [blame]
// Copyright (c) 2014, 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.
library coverage.src.devtools;
import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:io';
import 'package:logging/logging.dart';
final Logger _log = new Logger('coverage.src.devtools');
class VMService {
final _Connection _connection;
VMService._(this._connection);
Future<VM> getVM() async {
var response = await _connection.request('getVM');
return new VM.fromJson(response);
}
Future<Isolate> getIsolate(String isolateId) async {
var response =
await _connection.request('getIsolate', {'isolateId': isolateId});
return new Isolate.fromJson(response);
}
//TODO(kevmoo): Test this - https://github.com/dart-lang/coverage/issues/90
Future<AllocationProfile> getAllocationProfile(String isolateId,
{bool reset, bool gc}) async {
var params = {'isolateId': isolateId};
if (reset != null) {
params['reset'] = reset;
}
if (gc != null) {
params['gc'] = 'full';
}
// TODO(kevmoo) Remove fallback logic once 1.11 is stable
// https://github.com/dart-lang/coverage/issues/91
var response;
try {
// For Dart >=1.11.0-dev.3.0 - _getAllocationProfile is considered private
response = await _connection.request('_getAllocationProfile', params);
} on ServiceProtocolErrorBase catch (error) {
if (error.isMethodNotFound) {
// For Dart <1.11.0-dev.3.0 - getAllocationProfile is considered public
response = await _connection.request('getAllocationProfile', params);
} else {
rethrow;
}
}
return new AllocationProfile.fromJson(response);
}
Future<CodeCoverage> getCoverage(String isolateId, {String targetId}) async {
var params = {'isolateId': isolateId};
if (targetId != null) {
params['targetId'] = targetId;
}
var response;
// TODO(kevmoo) Remove fallback logic once 1.11 is stable
// https://github.com/dart-lang/coverage/issues/91
try {
// For Dart >=1.11.0-dev.3.0 - _getCoverage is considered private
response = await _connection.request('_getCoverage', params);
} on ServiceProtocolErrorBase catch (error) {
if (error.isMethodNotFound) {
// For Dart <1.11.0-dev.3.0 - getCoverage is considered public
response = await _connection.request('getCoverage', params);
} else {
rethrow;
}
}
return new CodeCoverage.fromJson(response);
}
Future resume(String isolateId) =>
_connection.request('resume', {'isolateId': isolateId});
static Future<VMService> connect(String host, int port) async {
_log.fine('Connecting to host $host on port $port');
return connectToVMWebsocket(host, port);
}
static Future<VMService> connectToVMWebsocket(String host, int port) async {
var connection = await _Connection.connect(host, port);
return new VMService._(connection);
}
Future close() => _connection.close();
}
class VM {
final String id;
final String targetCPU;
final String hostCPU;
final String version;
final String pid;
final List<IsolateRef> isolates;
VM(this.id, this.targetCPU, this.hostCPU, this.version, this.pid,
this.isolates);
factory VM.fromJson(json) => new VM(json['id'], json['targetCPU'],
json['hostCPU'], json['version'], json['pid'],
json['isolates'].map((i) => new IsolateRef.fromJson(i)).toList());
}
class IsolateRef {
final String id;
final String name;
IsolateRef(this.id, this.name);
factory IsolateRef.fromJson(json) => new IsolateRef(json['id'], json['name']);
}
class Isolate {
final String id;
final String name;
final bool pauseOnExit;
final ServiceEvent pauseEvent;
bool get paused =>
pauseOnExit && pauseEvent != null && pauseEvent.kind == 'PauseExit';
Isolate(this.id, this.name, this.pauseOnExit, this.pauseEvent);
factory Isolate.fromJson(json) => new Isolate(json['id'], json['name'],
json['pauseOnExit'], new ServiceEvent.fromJson(json['pauseEvent']));
}
class ServiceEvent {
final String kind;
final IsolateRef isolate;
@deprecated('Will be removed in 0.8')
String get eventType => kind;
ServiceEvent(this.kind, this.isolate);
factory ServiceEvent.fromJson(Map json) {
// TODO(kevmoo) Keep around until 1.11 is stable
// https://github.com/dart-lang/coverage/issues/91
// 'kind' is the key for >= 1.11-dev.5.0.
// 'eventType' is for older versions
var kind = json.containsKey('kind') ? json['kind'] : json['eventType'];
return new ServiceEvent(kind, new IsolateRef.fromJson(json['isolate']));
}
}
class CodeCoverage {
final String id;
final List coverage;
CodeCoverage(this.id, this.coverage);
factory CodeCoverage.fromJson(json) =>
new CodeCoverage(json['id'], json['coverage']);
}
class AllocationProfile {
final String id;
AllocationProfile(this.id);
factory AllocationProfile.fromJson(json) => new AllocationProfile(json['id']);
}
/// Observatory connection via websocket.
class _Connection {
final WebSocket _socket;
final Map<int, Completer> _pendingRequests = {};
int _requestId = 1;
_Connection(this._socket) {
_socket.listen(_handleResponse);
}
static Future<_Connection> connect(String host, int port) async {
_log.fine('Connecting to VM via HTTP websocket protocol');
var uri = 'ws://$host:$port/ws';
var socket = await WebSocket.connect(uri);
return new _Connection(socket);
}
Future<Map> request(String method, [Map params = const {}]) {
_pendingRequests[_requestId] = new Completer();
var message =
JSON.encode({'id': _requestId, 'method': method, 'params': params,});
_log.fine('Send> $message');
_socket.add(message);
return _pendingRequests[_requestId++].future;
}
Future close() => _socket.close();
void _handleResponse(String response) {
_log.fine('Recv< $response');
var json = JSON.decode(response);
var id = json['id'];
if (id is String) {
// Support for vm version >= 1.11.0
id = int.parse(id);
}
if (id == null || !_pendingRequests.keys.contains(id)) {
// Suppress unloved messages.
return;
}
var completer = _pendingRequests.remove(id);
if (completer == null) {
_log.severe('Failed to pair response with request');
}
// Behavior >= Dart 1.11-dev.3
var error = json['error'];
if (error != null) {
var errorObj = new JsonRpcError.fromJson(error);
completer.completeError(errorObj);
return;
}
var innerResponse = json['result'];
if (innerResponse == null) {
// Support for 1.9.0 <= vm version < 1.10.0.
innerResponse = json['response'];
}
if (innerResponse == null) {
completer.completeError('Failed to get JSON response for message $id');
return;
}
var message;
if (innerResponse != null) {
if (innerResponse is Map) {
// Support for vm version >= 1.11.0
message = innerResponse;
} else {
message = JSON.decode(innerResponse);
}
}
// need to check this for errors in the Dart 1.10 version
var type = message['type'];
if (type == 'Error') {
var errorObj = new Dart_1_10_RpcError.fromJson(message);
completer.completeError(errorObj);
return;
}
completer.complete(message);
}
}
abstract class ServiceProtocolErrorBase extends Error {
String get message;
bool get isMethodNotFound;
}
// TODO(kevmoo) Remove this logic once 1.11 is stable
// https://github.com/dart-lang/coverage/issues/91
class Dart_1_10_RpcError extends ServiceProtocolErrorBase {
final String message;
final bool isMethodNotFound;
Dart_1_10_RpcError(this.message, this.isMethodNotFound);
factory Dart_1_10_RpcError.fromJson(Map<String, dynamic> json) {
assert(json['type'] == 'Error');
var message = json['message'];
var isMethodNotFound = message.startsWith('unrecognized method:');
return new Dart_1_10_RpcError(message, isMethodNotFound);
}
}
class JsonRpcError extends ServiceProtocolErrorBase {
final int code;
final String message;
final data;
// http://www.jsonrpc.org/specification
// -32601 Method not found The method does not exist / is not available.
bool get isMethodNotFound => code == -32601;
JsonRpcError(this.code, this.message, this.data);
factory JsonRpcError.fromJson(Map<String, dynamic> json) =>
new JsonRpcError(json['code'], json['message'], json['data']);
String toString() {
var msg = 'JsonRpcError: $message';
if (isMethodNotFound) {
if (data is Map) {
var request = data['request'];
if (request is Map) {
var method = request['method'];
if (method != null) {
msg = '$msg - "$method"';
}
}
}
}
return '$msg ($code)';
}
}