blob: 79ee3de379507650719777b5b028f129ef35031a [file] [log] [blame]
// Copyright (c) 2019, 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.
part of dart.io;
// TODO(bkonyi): refactor into io_resource_info.dart
const int _versionMajor = 1;
const int _versionMinor = 6;
const String _tcpSocket = 'tcp';
const String _udpSocket = 'udp';
@pragma('vm:entry-point', !const bool.fromEnvironment("dart.vm.product"))
abstract class _NetworkProfiling {
// Http relative RPCs
@Deprecated('Use httpEnableTimelineLogging instead')
static const _kGetHttpEnableTimelineLogging =
'ext.dart.io.getHttpEnableTimelineLogging';
@Deprecated('Use httpEnableTimelineLogging instead')
static const _kSetHttpEnableTimelineLogging =
'ext.dart.io.setHttpEnableTimelineLogging';
static const _kHttpEnableTimelineLogging =
'ext.dart.io.httpEnableTimelineLogging';
static const _kGetHttpProfileRPC = 'ext.dart.io.getHttpProfile';
static const _kGetHttpProfileRequestRPC = 'ext.dart.io.getHttpProfileRequest';
static const _kClearHttpProfileRPC = 'ext.dart.io.clearHttpProfile';
// Socket relative RPCs
static const _kClearSocketProfileRPC = 'ext.dart.io.clearSocketProfile';
static const _kGetSocketProfileRPC = 'ext.dart.io.getSocketProfile';
static const _kSocketProfilingEnabledRPC =
'ext.dart.io.socketProfilingEnabled';
@Deprecated('Use socketProfilingEnabled instead')
static const _kPauseSocketProfilingRPC = 'ext.dart.io.pauseSocketProfiling';
@Deprecated('Use socketProfilingEnabled instead')
static const _kStartSocketProfilingRPC = 'ext.dart.io.startSocketProfiling';
// TODO(zichangguo): This version number represents the version of service
// extension of dart:io. Consider moving this out of web profiler class,
// if more methods added to dart:io,
static const _kGetVersionRPC = 'ext.dart.io.getVersion';
@pragma('vm:entry-point')
static void _registerServiceExtension() {
registerExtension(_kGetHttpEnableTimelineLogging, _serviceExtensionHandler);
registerExtension(_kSetHttpEnableTimelineLogging, _serviceExtensionHandler);
registerExtension(_kHttpEnableTimelineLogging, _serviceExtensionHandler);
registerExtension(_kGetSocketProfileRPC, _serviceExtensionHandler);
registerExtension(_kStartSocketProfilingRPC, _serviceExtensionHandler);
registerExtension(_kPauseSocketProfilingRPC, _serviceExtensionHandler);
registerExtension(_kSocketProfilingEnabledRPC, _serviceExtensionHandler);
registerExtension(_kClearSocketProfileRPC, _serviceExtensionHandler);
registerExtension(_kGetVersionRPC, _serviceExtensionHandler);
registerExtension(_kGetHttpProfileRPC, _serviceExtensionHandler);
registerExtension(_kGetHttpProfileRequestRPC, _serviceExtensionHandler);
registerExtension(_kClearHttpProfileRPC, _serviceExtensionHandler);
}
static Future<ServiceExtensionResponse> _serviceExtensionHandler(
String method, Map<String, String> parameters) {
try {
String responseJson;
switch (method) {
case _kGetHttpEnableTimelineLogging:
responseJson = _getHttpEnableTimelineLogging();
break;
case _kSetHttpEnableTimelineLogging:
responseJson = _setHttpEnableTimelineLogging(parameters);
break;
case _kHttpEnableTimelineLogging:
if (parameters.containsKey('enabled') ||
parameters.containsKey('enable')) {
// TODO(bkonyi): Backwards compatibility.
// See https://github.com/dart-lang/sdk/issues/43638.
assert(_versionMajor == 1,
"'enable' is deprecated and should be removed (See #43638)");
if (parameters.containsKey('enabled')) {
parameters['enable'] = parameters['enabled']!;
}
_setHttpEnableTimelineLogging(parameters);
}
responseJson = _getHttpEnableTimelineLogging();
break;
case _kGetHttpProfileRPC:
responseJson = HttpProfiler.toJson(
parameters.containsKey('updatedSince')
? int.tryParse(parameters['updatedSince']!)
: null,
);
break;
case _kGetHttpProfileRequestRPC:
responseJson = _getHttpProfileRequest(parameters);
break;
case _kClearHttpProfileRPC:
HttpProfiler.clear();
responseJson = _success();
break;
case _kGetSocketProfileRPC:
responseJson = _SocketProfile.toJson();
break;
case _kSocketProfilingEnabledRPC:
responseJson = _socketProfilingEnabled(parameters);
break;
case _kStartSocketProfilingRPC:
responseJson = _SocketProfile.start();
break;
case _kPauseSocketProfilingRPC:
responseJson = _SocketProfile.pause();
break;
case _kClearSocketProfileRPC:
responseJson = _SocketProfile.clear();
break;
case _kGetVersionRPC:
responseJson = getVersion();
break;
default:
return Future.value(
ServiceExtensionResponse.error(
ServiceExtensionResponse.extensionError,
'Method $method does not exist',
),
);
}
return Future.value(
ServiceExtensionResponse.result(responseJson),
);
} catch (errorMessage) {
return Future.value(
ServiceExtensionResponse.error(
ServiceExtensionResponse.invalidParams,
errorMessage.toString(),
),
);
}
}
static String getVersion() => json.encode({
'type': 'Version',
'major': _versionMajor,
'minor': _versionMinor,
});
}
String _success() => json.encode({
'type': 'Success',
});
String _invalidArgument(String argument, dynamic value) =>
"Value for parameter '$argument' is not valid: $value";
String _missingArgument(String argument) => "Parameter '$argument' is required";
String _getHttpEnableTimelineLogging() => json.encode({
'type': 'HttpTimelineLoggingState',
'enabled': HttpClient.enableTimelineLogging,
});
String _setHttpEnableTimelineLogging(Map<String, String> parameters) {
const String kEnable = 'enable';
if (!parameters.containsKey(kEnable)) {
throw _missingArgument(kEnable);
}
final enable = parameters[kEnable]!.toLowerCase();
if (enable != 'true' && enable != 'false') {
throw _invalidArgument(kEnable, enable);
}
HttpClient.enableTimelineLogging = enable == 'true';
return _success();
}
String _getHttpProfileRequest(Map<String, String> parameters) {
if (!parameters.containsKey('id')) {
throw _missingArgument('id');
}
final id = int.tryParse(parameters['id']!);
if (id == null) {
throw _invalidArgument('id', parameters['id']!);
}
final request = HttpProfiler.getHttpProfileRequest(id);
if (request == null) {
throw "Unable to find request with id: '$id'";
}
return json.encode(
request.toJson(ref: false),
);
}
String _socketProfilingEnabled(Map<String, String> parameters) {
const String kEnabled = 'enabled';
if (parameters.containsKey(kEnabled)) {
final enable = parameters[kEnabled]!.toLowerCase();
if (enable != 'true' && enable != 'false') {
throw _invalidArgument(kEnabled, enable);
}
enable == 'true' ? _SocketProfile.start() : _SocketProfile.pause();
}
return json.encode({
'type': 'SocketProfilingState',
'enabled': _SocketProfile.enableSocketProfiling,
});
}
abstract class _SocketProfile {
static const _kType = 'SocketProfile';
static set enableSocketProfiling(bool enabled) {
if (enabled != _enableSocketProfiling) {
postEvent('SocketProfilingStateChange', {
'isolateId': Service.getIsolateID(Isolate.current),
'enabled': enabled,
});
_enableSocketProfiling = enabled;
}
}
static bool get enableSocketProfiling => _enableSocketProfiling;
static bool _enableSocketProfiling = false;
static Map<int, _SocketStatistic> _idToSocketStatistic = {};
static String toJson() => json.encode({
'type': _kType,
'sockets': _idToSocketStatistic.values.map((f) => f.toMap()).toList(),
});
static void collectNewSocket(
int id, String type, InternetAddress addr, int port) {
_SocketProfile.collectStatistic(id, _SocketProfileType.startTime);
_SocketProfile.collectStatistic(id, _SocketProfileType.socketType, type);
_SocketProfile.collectStatistic(id, _SocketProfileType.address, addr);
_SocketProfile.collectStatistic(id, _SocketProfileType.port, port);
}
static void collectStatistic(int id, _SocketProfileType type,
[dynamic object]) {
if (!_enableSocketProfiling) {
return;
}
// Skip socket that started before _enableSocketProfiling turned on.
if (!_idToSocketStatistic.containsKey(id) &&
type != _SocketProfileType.startTime) return;
_SocketStatistic stats = _idToSocketStatistic[id] ??= _SocketStatistic(id);
switch (type) {
case _SocketProfileType.startTime:
stats.startTime = Timeline.now;
break;
case _SocketProfileType.endTime:
stats.endTime = Timeline.now;
break;
case _SocketProfileType.address:
assert(object is InternetAddress);
stats.address = (object as InternetAddress).toString();
break;
case _SocketProfileType.port:
assert(object is int);
stats.port = object;
break;
case _SocketProfileType.socketType:
assert(object is String);
stats.socketType = object;
break;
case _SocketProfileType.readBytes:
if (object == null) return;
stats.readBytes += object as int;
stats.lastReadTime = Timeline.now;
break;
case _SocketProfileType.writeBytes:
if (object == null) return;
stats.writeBytes += object as int;
stats.lastWriteTime = Timeline.now;
break;
default:
throw ArgumentError('type ${type} does not exist');
}
}
static String start() {
enableSocketProfiling = true;
return _success();
}
static String pause() {
enableSocketProfiling = false;
return _success();
}
// clear the storage if _idToSocketStatistic has been initialized.
static String clear() {
_idToSocketStatistic.clear();
return _success();
}
}
/// The [_SocketProfileType] is used as a parameter for
/// [_SocketProfile.collectStatistic] to determine the type of statistic.
enum _SocketProfileType {
startTime,
endTime,
address,
port,
socketType,
readBytes,
writeBytes
}
/// Socket statistic
class _SocketStatistic {
final int id;
int? startTime;
int? endTime;
String? address;
int? port;
String? socketType;
int readBytes = 0;
int writeBytes = 0;
int? lastWriteTime;
int? lastReadTime;
_SocketStatistic(this.id);
Map<String, dynamic> toMap() {
final map = <String, dynamic>{
'id': id,
};
_setIfNotNull(map, 'startTime', startTime);
_setIfNotNull(map, 'endTime', endTime);
_setIfNotNull(map, 'address', address);
_setIfNotNull(map, 'port', port);
_setIfNotNull(map, 'socketType', socketType);
_setIfNotNull(map, 'readBytes', readBytes);
_setIfNotNull(map, 'writeBytes', writeBytes);
_setIfNotNull(map, 'lastWriteTime', lastWriteTime);
_setIfNotNull(map, 'lastReadTime', lastReadTime);
return map;
}
void _setIfNotNull(Map<String, dynamic> json, String key, Object? value) {
if (value == null) return;
json[key] = value;
}
}