[VM/Service] Update dart:io service extension spec

The main purpose of these changes is to make sure that the types in
package:http_profile are compatible with the interfaces in the spec.

See runtime/vm/service/service_extension.md and
pkg/vm_service/CHANGELOG.md for the full list of changes.

TEST=pkg/vm_service/test/get_http_profile_test.dart, DevTools tests

CoreLibraryReviewExempt: Only touches sdk/lib/io/network_profiling.dart
to update the version returned by the getVersion dart:io service
extension.
Change-Id: I1b9d0b7d43defbc857a2a8fde003012effd1ee15
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341120
Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Derek Xu 2024-02-06 19:26:06 +00:00
parent e8b20f66b2
commit 60833d9cd9
7 changed files with 272 additions and 205 deletions

View file

@ -1,4 +1,4 @@
## 13.1.0-dev
## 14.0.0
- Add the following error codes to `RPCErrorKind`:
- `kVmMustBePaused`
- `kCannotAddBreakpoint`
@ -8,6 +8,32 @@
- `kIsolateCannotReload`
- `kIsolateNoReloadChangesApplied`
- `kInvalidTimelineRequest`
- Update to version `4.0` of the Dart IO service protocol extensions by making
the following changes:
- Change the type of the `updatedSince` parameter of `getHttpProfile` from
`int?` to `DateTime?`.
- Change the type of the `timestamp` property of `HttpProfile` from `int` to
`DateTime`.
- Add `events` property to `HttpProfileRequestRef` and `HttpProfileRequest`.
- Change the type of the `startTime` property of `HttpProfileRequestRef` and
`HttpProfileRequest` from `int` to `DateTime`.
- Change the type of the `endTime` property of `HttpProfileRequestRef` and
`HttpProfileRequest` from `int?` to `DateTime?`.
- Remove the `events` and `method` properties from `HttpProfileRequestData`.
- Make the `contentLength`, `cookies`, `followRedirects`, `headers`,
`maxRedirects`, `method`, and `persistentConnection` properties of
`HttpProfileRequestData` nullable.
- Change the type of the `startTime` property of `HttpProfileResponseData`
from `int` to `DateTime?`.
- Change the type of the `endTime` property of `HttpProfileResponseData`
from `int?` to `DateTime?`.
- Make the `cookies`, `headers`, `compressionState`, `reasonPhrase`,
`isRedirect`, `persistentConnection`, `contentLength`, `statusCode`, and
`startTime` properties of `HttpProfileResponseData` nullable.
- Add `isDirect` and `port` properties to `HttpProfileProxyData`.
- Add `arguments` property to `HttpProfileRequestEvent`.
- Change the type of the `timestamp` property of `HttpProfileRequestEvent`
from `int` to `DateTime`.
## 13.0.0
- Add Dart IO extension methods:
@ -98,7 +124,7 @@ instance and connect it to a web socket URI.
- Change `HttpProfileRequestRef.id` type from `int` to `String`.
- Change `SocketStatistic.id` type from `int` to `String`.
- Change `ext.dart.io.getHttpProfileRequest` `id` parameter type from `int` to `String`.
- Change `ext.dart.io.socketProfilingEnabled` parameter from 'enable' to 'enabled'.
- Change `ext.dart.io.httpEnableTimelineLogging` parameter from 'enable' to 'enabled'.
## 10.1.2
- Fix bug where code would try to call `.toJson()` on `String`s.

View file

@ -111,11 +111,12 @@ extension DartIOExtension on VmService {
/// the specified time will be reported.
Future<HttpProfile> getHttpProfile(
String isolateId, {
int? updatedSince,
DateTime? updatedSince,
}) async {
assert(await isHttpProfilingAvailable(isolateId));
return _callHelper('ext.dart.io.getHttpProfile', isolateId, args: {
if (updatedSince != null) 'updatedSince': updatedSince,
if (updatedSince != null)
'updatedSince': updatedSince.microsecondsSinceEpoch,
});
}
@ -320,10 +321,11 @@ class HttpProfile extends Response {
json == null ? null : HttpProfile._fromJson(json);
HttpProfile._fromJson(Map<String, dynamic> json)
: timestamp = json['timestamp'],
requests = (json['requests'] as List)
: timestamp = DateTime.fromMicrosecondsSinceEpoch(json['timestamp']!),
requests = json['requests']!
.cast<Map<String, dynamic>>()
.map((e) => HttpProfileRequest._fromJson(e))
.cast<HttpProfileRequest>()
.toList();
HttpProfile({required this.requests, required this.timestamp});
@ -334,8 +336,8 @@ class HttpProfile extends Response {
@override
String toString() => '[HttpProfile]';
/// The time at which this HTTP profile was built, in microseconds.
final int timestamp;
/// The time at which this HTTP profile was built.
final DateTime timestamp;
/// The set of recorded HTTP requests.
final List<HttpProfileRequest> requests;
@ -347,12 +349,19 @@ class HttpProfileRequestRef {
json == null ? null : HttpProfileRequestRef._fromJson(json);
HttpProfileRequestRef._fromJson(Map<String, dynamic> json)
: isolateId = json['isolateId'],
id = json['id'],
method = json['method'],
uri = Uri.parse(json['uri']),
startTime = json['startTime'],
endTime = json['endTime'],
: isolateId = json['isolateId']!,
id = json['id']!,
method = json['method']!,
uri = Uri.parse(json['uri']!),
events = json['events']!
.cast<Map<String, dynamic>>()
.map((e) => HttpProfileRequestEvent._fromJson(e))
.cast<HttpProfileRequestEvent>()
.toList(),
startTime = DateTime.fromMicrosecondsSinceEpoch(json['startTime']!),
endTime = json['endTime'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['endTime']),
request = HttpProfileRequestData.parse(json['request']),
response = HttpProfileResponseData.parse(json['response']);
@ -361,6 +370,7 @@ class HttpProfileRequestRef {
required this.id,
required this.method,
required this.uri,
required this.events,
required this.startTime,
this.endTime,
this.request,
@ -372,22 +382,26 @@ class HttpProfileRequestRef {
/// The ID associated with this request.
///
/// This ID corresponds to the ID of the timeline event for this request.
/// If the ID does not start with the prefix 'from_package/', then there
/// will be a corresponding timeline event with the same ID.
final String id;
/// The HTTP request method associated with this request.
final String method;
/// The URI for this HTTP request.
/// The URI to which this HTTP request was sent.
final Uri uri;
/// The time at which this request was initiated, in microseconds.
final int startTime;
/// Events related to this HTTP request.
final List<HttpProfileRequestEvent> events;
/// The time at which this request was completed, in microseconds.
/// The time at which this request was initiated.
final DateTime startTime;
/// The time at which this request was completed.
///
/// Will be `null` if the request is still in progress.
final int? endTime;
final DateTime? endTime;
/// Returns `true` if the initial HTTP request has completed.
bool get isRequestComplete => endTime != null;
@ -395,12 +409,12 @@ class HttpProfileRequestRef {
/// Returns `true` if the entirety of the response has been received.
bool get isResponseComplete => response?.isComplete ?? false;
/// Information sent as part of the initial HTTP request.
/// Details about the request.
///
/// Can be `null` if the request has not yet been completed.
final HttpProfileRequestData? request;
/// Information received in response to the initial HTTP request.
/// Details about the response.
///
/// Can be `null` if the request has not yet been responded to.
final HttpProfileResponseData? response;
@ -424,12 +438,13 @@ class HttpProfileRequest extends HttpProfileRequestRef {
required super.isolateId,
required super.method,
required super.uri,
required super.events,
required super.startTime,
required this.requestBody,
required this.responseBody,
super.endTime,
super.request,
super.response,
this.requestBody,
this.responseBody,
});
/// The body sent as part of this request.
@ -448,54 +463,45 @@ class HttpProfileRequestData {
json == null ? null : HttpProfileRequestData._fromJson(json);
HttpProfileRequestData._fromJson(Map<String, dynamic> json)
: _headers = json['headers'],
: _headers = UnmodifiableMapView(json['headers'] ?? {}),
_connectionInfo = UnmodifiableMapView(json['connectionInfo'] ?? {}),
_contentLength = json['contentLength'],
_cookies = UnmodifiableListView(json['cookies']?.cast<String>() ?? []),
_followRedirects = json['followRedirects'] ?? false,
_maxRedirects = json['maxRedirects'] ?? 0,
_method = json['method'],
_persistentConnection = json['persistentConnection'] ?? false,
proxyDetails = HttpProfileProxyData.parse(json['proxyDetails']),
error = json['error'],
events = UnmodifiableListView((json['events'] as List)
.cast<Map<String, dynamic>>()
.map((e) => HttpProfileRequestEvent._fromJson(e))
.toList());
_proxyDetails = HttpProfileProxyData.parse(json['proxyDetails']),
error = json['error'];
HttpProfileRequestData.buildSuccessfulRequest({
required Map<String, dynamic> headers,
required Map<String, dynamic>? connectionInfo,
required int contentLength,
Map<String, dynamic>? headers,
Map<String, dynamic>? connectionInfo,
int? contentLength,
required List<String> cookies,
required bool followRedirects,
required int maxRedirects,
required String method,
required bool persistentConnection,
required this.events,
this.proxyDetails,
bool? followRedirects,
int? maxRedirects,
bool? persistentConnection,
HttpProfileProxyData? proxyDetails,
}) : _headers = headers,
_connectionInfo = connectionInfo,
_contentLength = contentLength,
_cookies = cookies,
_followRedirects = followRedirects,
_maxRedirects = maxRedirects,
_method = method,
_persistentConnection = persistentConnection,
_proxyDetails = proxyDetails,
error = null;
HttpProfileRequestData.buildErrorRequest({
required this.error,
required this.events,
}) : _connectionInfo = null,
_contentLength = null,
_cookies = [],
_cookies = null,
_followRedirects = null,
_headers = null,
_maxRedirects = null,
_method = null,
_persistentConnection = null,
proxyDetails = null;
_proxyDetails = null;
/// Returns `true` if an error has occurred while issuing the request.
///
@ -504,72 +510,61 @@ class HttpProfileRequestData {
bool get hasError => error != null;
/// Information about the client connection.
Map<String, dynamic>? get connectionInfo {
return _connectionInfo == null
? null
: UnmodifiableMapView(_connectionInfo);
}
///
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
Map<String, dynamic>? get connectionInfo => _returnIfNoError(_connectionInfo);
final Map<String, dynamic>? _connectionInfo;
/// The content length of the request, in bytes.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
int get contentLength => _returnIfNoError(_contentLength);
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
int? get contentLength => _returnIfNoError(_contentLength);
final int? _contentLength;
/// Cookies presented to the server (in the 'cookie' header).
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
List<String> get cookies => _returnIfNoError(_cookies);
final List<String> _cookies;
/// Events that has occurred while issuing this HTTP request.
final List<HttpProfileRequestEvent> events;
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
List<String>? get cookies => _returnIfNoError(_cookies);
final List<String>? _cookies;
/// The error associated with the failed request.
final String? error;
/// Whether redirects are followed automatically.
/// Whether automatic redirect following was enabled for the request.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
bool get followRedirects => _returnIfNoError(_followRedirects);
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
bool? get followRedirects => _returnIfNoError(_followRedirects);
final bool? _followRedirects;
/// Returns the client request headers.
/// The client request headers.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
Map<String, dynamic> get headers => UnmodifiableMapView(
_returnIfNoError(_headers),
);
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
Map<String, dynamic>? get headers => _returnIfNoError(_headers);
final Map<String, dynamic>? _headers;
/// The maximum number of redirects to follow when `followRedirects` is true.
/// The maximum number of redirects allowed during the request.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
int get maxRedirects => _returnIfNoError(_maxRedirects);
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
int? get maxRedirects => _returnIfNoError(_maxRedirects);
final int? _maxRedirects;
/// The method of the request.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
String get method => _returnIfNoError(_method);
final String? _method;
/// The requested persistent connection state.
///
/// Throws [HttpProfileRequestError] is `hasError` is `true`.
bool get persistentConnection => _returnIfNoError(_persistentConnection);
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
bool? get persistentConnection => _returnIfNoError(_persistentConnection);
final bool? _persistentConnection;
/// Proxy authentication details for this request.
final HttpProfileProxyData? proxyDetails;
///
/// Throws [HttpProfileRequestError] if `hasError` is `true`.
HttpProfileProxyData? get proxyDetails => _returnIfNoError(_proxyDetails);
final HttpProfileProxyData? _proxyDetails;
T _returnIfNoError<T>(T? field) {
T? _returnIfNoError<T>(T? field) {
if (hasError) {
throw HttpProfileRequestError(error!);
}
return field!;
return field;
}
}
@ -587,40 +582,45 @@ class HttpProfileRequestError implements Error {
String toString() => 'HttpProfileRequestError: $error.';
}
/// Proxy authentication details associated with an [HttpProfileRequest].
/// Describes proxy authentication details associated with an
/// [HttpProfileRequest].
class HttpProfileProxyData {
static HttpProfileProxyData? parse(Map<String, dynamic>? json) =>
json == null ? null : HttpProfileProxyData._fromJson(json);
HttpProfileProxyData._fromJson(Map<String, dynamic> json)
: host = json['timestamp'],
port = json['event'],
username = json['arguments'];
: host = json['host'],
username = json['username'],
isDirect = json['isDirect'],
port = json['port'];
HttpProfileProxyData({
this.host,
this.username,
this.isDirect,
this.port,
});
/// The URI of the proxy server.
final String? host;
/// The port the proxy server is listening on.
final int? port;
/// The username used to authenticate with the proxy server.
final String? username;
final bool? isDirect;
/// The port the proxy server is listening on.
final int? port;
}
/// Describes an event that has occurred while issuing a HTTP request.
/// Describes an event related to an HTTP request.
class HttpProfileRequestEvent {
static HttpProfileRequestEvent? parse(Map<String, dynamic>? json) =>
json == null ? null : HttpProfileRequestEvent._fromJson(json);
HttpProfileRequestEvent._fromJson(Map<String, dynamic> json)
: timestamp = json['timestamp'],
event = json['event'],
: timestamp = DateTime.fromMicrosecondsSinceEpoch(json['timestamp']!),
event = json['event']!,
arguments = json['arguments'];
HttpProfileRequestEvent({
@ -629,9 +629,13 @@ class HttpProfileRequestEvent {
this.arguments,
});
final Map<String, dynamic>? arguments;
/// The title of the recorded event.
final String event;
final int timestamp;
/// The time at which the event occurred.
final DateTime timestamp;
final Map<String, dynamic>? arguments;
}
/// Information received in response to an initial HTTP request.
@ -640,13 +644,17 @@ class HttpProfileResponseData {
json == null ? null : HttpProfileResponseData._fromJson(json);
HttpProfileResponseData._fromJson(Map<String, dynamic> json)
: startTime = json['startTime']!,
endTime = json['endTime'],
headers = json['headers']!,
connectionInfo = json['connectionInfo']!,
contentLength = json['contentLength']!,
compressionState = json['compressionState']!,
cookies = UnmodifiableListView(json['cookies']!.cast<String>()),
: startTime = json['startTime'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['startTime']),
endTime = json['endTime'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['endTime']),
headers = json['headers'],
connectionInfo = json['connectionInfo'],
contentLength = json['contentLength'],
compressionState = json['compressionState'],
cookies = UnmodifiableListView(json['cookies']?.cast<String>() ?? []),
error = json['error'],
isRedirect = json['isRedirect'],
persistentConnection = json['persistentConnection'],
@ -656,38 +664,38 @@ class HttpProfileResponseData {
statusCode = json['statusCode'];
HttpProfileResponseData({
required this.startTime,
this.startTime,
this.endTime,
required this.headers,
required this.compressionState,
required this.connectionInfo,
required this.contentLength,
required this.cookies,
required this.isRedirect,
required this.persistentConnection,
required this.reasonPhrase,
this.headers,
this.compressionState,
this.connectionInfo,
this.contentLength,
this.cookies,
this.isRedirect,
this.persistentConnection,
this.reasonPhrase,
required this.redirects,
required this.statusCode,
this.statusCode,
this.error,
});
bool get isComplete => endTime != null;
bool get hasError => error != null;
/// Returns the series of redirects this connection has been through.
/// The series of redirects this connection has been through.
///
/// The list will be empty if no redirects were followed. redirects will be
/// The list will be empty if no redirects were followed. Redirects will be
/// updated both in the case of an automatic and a manual redirect.
final List<Map<String, dynamic>> redirects;
/// Cookies set by the server (from the 'set-cookie' header).
final List<String> cookies;
final List<String>? cookies;
/// Information about the client connection.
final Map<String, dynamic>? connectionInfo;
/// Returns the client response headers.
final Map<String, dynamic> headers;
/// The client response headers.
final Map<String, dynamic>? headers;
/// The compression state of the response.
///
@ -696,32 +704,32 @@ class HttpProfileResponseData {
/// uncompressed bytes when they listed to this response's byte stream.
///
/// See [HttpClientResponseCompressionState](https://api.dart.dev/stable/dart-io/HttpClientResponseCompressionState-class.html) for possible values.
final String compressionState;
final String? compressionState;
/// Returns the reason phrase associated with the status code.
final String reasonPhrase;
/// The reason phrase associated with the status code.
final String? reasonPhrase;
/// Returns whether the status code is one of the normal redirect codes.
final bool isRedirect;
/// Whether the status code is one of the normal redirect codes.
final bool? isRedirect;
/// The persistent connection state returned by the server.
final bool persistentConnection;
final bool? persistentConnection;
/// Returns the content length of the response body.
/// The content length of the response body, in bytes.
///
/// Returns -1 if the size of the response body is not known in advance.
final int contentLength;
final int? contentLength;
/// Returns the status code.
final int statusCode;
/// The status code.
final int? statusCode;
/// The time at which the initial response was received, in microseconds.
final int startTime;
/// The time at which the initial response was received.
final DateTime? startTime;
/// The time at which the response was completed, in microseconds.
/// The time at which the response was completed.
///
/// Will be `null` if response data is still being received.
final int? endTime;
final DateTime? endTime;
/// The error associated with the failed response.
final String? error;

View file

@ -1,5 +1,5 @@
name: vm_service
version: 13.0.0
version: 14.0.0
description: >-
A library to communicate with a service implementing the Dart VM
service protocol.

View file

@ -71,6 +71,10 @@ Uri randomlyAddRequestParams(Uri uri) {
Future<HttpServer> startServer() async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((request) async {
// Randomly delay starting the response.
await Future.delayed(
Duration(milliseconds: rng.nextInt(maxResponseDelayMs) + 1),
);
final response = request.response;
response.write(request.method);
randomlyAddCookie(response);
@ -78,9 +82,9 @@ Future<HttpServer> startServer() async {
// Redirect calls close() on the response.
return;
}
// Randomly delay response.
// Randomly delay finishing the response.
await Future.delayed(
Duration(milliseconds: rng.nextInt(maxResponseDelayMs)),
Duration(milliseconds: rng.nextInt(maxResponseDelayMs) + 1),
);
await response.close();
});
@ -206,7 +210,7 @@ Future<void> hasValidHttpRequests(HttpProfile profile, String method) async {
expect(requestData.error!.isNotEmpty, true);
// Some data is available even if a request errored out.
expect(requestData.events.length, greaterThanOrEqualTo(0));
expect(r.events.length, greaterThanOrEqualTo(0));
expect(fullRequest.requestBody!.length, greaterThanOrEqualTo(0));
// Accessing the following properties should cause an exception for
@ -216,7 +220,6 @@ Future<void> hasValidHttpRequests(HttpProfile profile, String method) async {
expectThrows(() => requestData.followRedirects);
expectThrows(() => requestData.headers);
expectThrows(() => requestData.maxRedirects);
expectThrows(() => requestData.method);
expectThrows(() => requestData.persistentConnection);
} else {
// Invoke all non-nullable getters to ensure each is present in the JSON
@ -255,8 +258,8 @@ Future<void> hasValidHttpRequests(HttpProfile profile, String method) async {
final responseData = r.response!;
expect(responseData.statusCode, greaterThanOrEqualTo(100));
expect(responseData.endTime, isNotNull);
expect(responseData.startTime > r.endTime!, true);
expect(responseData.endTime! >= responseData.startTime, true);
expect(responseData.startTime!.isAfter(r.endTime!), true);
expect(responseData.startTime!.isBefore(responseData.endTime!), true);
expect(utf8.decode(fullRequest.responseBody!), method);
responseData.headers;
responseData.compressionState;
@ -301,8 +304,8 @@ void hasDefaultRequestHeaders(HttpProfile profile) {
// requests.
if (!request.isRequestComplete) continue;
if (!request.request!.hasError) {
expect(request.request?.headers['host'], isNotNull);
expect(request.request?.headers['user-agent'], isNotNull);
expect(request.request?.headers?['host'], isNotNull);
expect(request.request?.headers?['user-agent'], isNotNull);
}
}
}
@ -315,7 +318,7 @@ void hasCustomRequestHeaders(HttpProfile profile) {
// requests.
if (!request.isRequestComplete) continue;
if (!request.request!.hasError) {
expect(request.request?.headers['cookie-eater'], isNotNull);
expect(request.request?.headers?['cookie-eater'], isNotNull);
}
}
}

View file

@ -1,4 +1,4 @@
# Dart VM Service Protocol Extension 1.6
# Dart VM Service Protocol Extension 4.0
This protocol describes service extensions that are made available through
the Dart core libraries, but are not part of the core
@ -10,7 +10,7 @@ invoked by prepending the service extension name (e.g.,
## dart:io Extensions
This section describes _version 1.6_ of the dart:io service protocol extensions.
This section describes _version 4.0_ of the dart:io service protocol extensions.
### getVersion
@ -130,8 +130,9 @@ The returned `HttpProfile` will only include requests issued after
`httpTimelineLogging` has been enabled or after the last
`clearHttpProfile` invocation.
If `updatedSince` is provided, only requests started or updated since
the specified time will be reported.
If `updatedSince` is provided, only requests started or updated since the
specified time will be reported. The specified time must be represented in
microseconds since the "Unix epoch".
See [HttpProfile](#httpprofile).
@ -172,7 +173,7 @@ class @OpenFile extends Response {
}
```
_@File_ is a reference to a _File_.
_@OpenFile_ is a reference to an _OpenFile_.
```
class OpenFile extends Response {
@ -230,7 +231,8 @@ See [httpEnableTimelineLogging](#httpenabletimelinelogging).
```
class HttpProfile extends Response {
// The time at which this HTTP profile was built, in microseconds.
// The time at which this HTTP profile was built, represented as microseconds
// since the "Unix epoch".
int timestamp;
// The set of recorded HTTP requests.
@ -248,8 +250,9 @@ See [getHttpProfile](#gethttpprofile).
class @HttpProfileRequest extends Response {
// The ID associated with this request.
//
// This ID corresponds to the ID of the timeline event for this request.
int id;
// If the ID does not start with the prefix "from_package/", then there
// will be a corresponding timeline event with the same ID.
string id;
// The ID of the isolate this request was issued from.
string isolateId;
@ -257,21 +260,28 @@ class @HttpProfileRequest extends Response {
// The HTTP request method associated with this request.
string method;
// The URI for this HTTP request.
// The URI to which this HTTP request was sent.
string uri;
// The time at which this request was initiated, in microseconds.
final int startTime;
// Events related to this HTTP request.
//
// Events which occurred before encountering an error will be reported.
HttpProfileRequestEvent[] events;
// The time at which this request was completed, in microseconds.
// The time at which this request was initiated, represented as microseconds
// since the "Unix epoch".
int startTime;
// The time at which this request was completed, represented as microseconds
// since the "Unix epoch".
int endTime [optional];
// Information sent as part of the initial HTTP request.
// Details about the request.
//
// Will not be provided if the initial request has not yet completed.
HttpProfileRequestData request [optional];
// Information received in response to the initial HTTP request.
// Details about the response.
//
// Will not be provided if the request has not yet been responded to.
HttpProfileResponseData response [optional];
@ -300,36 +310,26 @@ See [HttpProfile](#httpprofile).
```
class HttpProfileRequestData {
// Information about the client connection.
//
// This property can be null, regardless of error state.
map<string, dynamic> connectionInfo [optional];
// The content length of the request, in bytes.
int contentLength [optional];
// Cookies presented to the server (in the 'cookie' header).
string[] cookies;
// Events that has occurred while issuing this HTTP request.
//
// Events which occurred before encountering an error will be reported.
HttpProfileRequestEvent[] events;
string[] cookies [optional];
// The error associated with the failed request.
string error [optional];
// Whether to redirects are followed automatically.
// Whether automatic redirect following was enabled for the request.
bool followRedirects [optional];
// Returns the client request headers.
// The client request headers.
map<string, dynamic> headers [optional];
// The maximum number of redirects to follow when `followRedirects` is true.
// The maximum number of redirects allowed during the request.
int maxRedirects [optional];
// The method of the request.
string method [optional];
// The requested persistent connection state.
bool persistentConnection [optional];
@ -339,8 +339,7 @@ class HttpProfileRequestData {
```
Information sent as part of the initial HTTP request. If `error` is present,
other properties will be null. If `error` is not present, all other properties
will be provided unless otherwise specified.
the other properties will be null.
See [HttpProfileRequest](#httpprofilerequest).
@ -348,49 +347,51 @@ See [HttpProfileRequest](#httpprofilerequest).
```
class HttpProfileResponseData {
// Returns the series of redirects this connection has been through.
// The series of redirects this connection has been through.
//
// The list will be empty if no redirects were followed. redirects will be
// The list will be empty if no redirects were followed. Redirects will be
// updated both in the case of an automatic and a manual redirect.
map<string, dynamic>[] redirects;
// Cookies set by the server (from the 'set-cookie' header).
string[] cookies;
string[] cookies [optional];
// Information about the client connection.
map<string, dynamic> connectionInfo [optional];
// Returns the client response headers.
map<string, dynamic> headers;
// The client response headers.
map<string, dynamic> headers [optional];
// The compression state of the response.
//
// This specifies whether the response bytes were compressed when they were
// received across the wire and whether callers will receive compressed or
// uncompressed bytes when they listed to this response's byte stream.
string compressionState;
string compressionState [optional];
// Returns the reason phrase associated with the status code.
string reasonPhrase;
// The reason phrase associated with the status code.
string reasonPhrase [optional];
// Returns whether the status code is one of the normal redirect codes.
bool isRedirect;
// Whether the status code is one of the normal redirect codes.
bool isRedirect [optional];
// The persistent connection state returned by the server.
bool persistentConnection;
bool persistentConnection [optional];
// Returns the content length of the response body.
// The content length of the response body, in bytes.
//
// Returns -1 if the size of the response body is not known in advance.
int contentLength;
int contentLength [optional];
// Returns the status code.
int statusCode;
// The status code.
int statusCode [optional];
// The time at which the initial response was received, in microseconds.
int startTime;
// The time at which the initial response was received, represented as
// microseconds since the "Unix epoch".
int startTime [optional];
// The time at which the response was completed, in microseconds.
// The time at which the response was completed, represented as
// microseconds since the "Unix epoch".
int endTime [optional];
// The error associated with the failed request.
@ -408,12 +409,12 @@ See [HttpProfileRequest](#httpprofilerequest).
class HttpProfileProxyData {
string host [optional];
string username [optional];
bool isDirect;
bool isDirect [optional];
int port [optional];
}
```
Proxy authentication details associated with a HTTP request.
Proxy authentication details associated with an HTTP request.
See [HttpProfileRequestData](#httpprofilerequestdata).
@ -424,7 +425,8 @@ class HttpProfileRequestEvent {
// The title of the recorded event.
string event;
// The time at which the event occurred, in microseconds.
// The time at which the event occurred, represented as microseconds since
// the "Unix epoch".
int timestamp;
// Any arguments recorded for the event.
@ -432,7 +434,7 @@ class HttpProfileRequestEvent {
}
```
Describes an event that has occurred while issuing a HTTP request.
Describes an event related to an HTTP request.
See [HttpProfileRequestData](#httpprofilerequestdata).
@ -590,3 +592,31 @@ version | comments
1.5 | Added `socketProfilingEnabled` RPC and `SocketProfilingStateChanged` event, deprecated `startSocketProfiling` and `pauseSocketProfiling`.
1.6 | Added `isSocketProfilingAvailable`, `isHttpTimelineLoggingAvailable`, `isHttpProfilingAvailable`, removed deprecated RPCs `startSocketProfiling`,
`pauseSocketProfiling`, `getHttpEnableTimelineLogging`, and `setHttpEnableTimelineLogging`.
2.0 | Changed the type of the `id` property of `@HttpProfileRequestRef` and
`HttpProfileRequestRef` from `int` to `String`. Changed the type of
`SocketStatistic.id` from `int` to `String`. Changed the type of the `id`
parameter of `getHttpProfileRequest` from `int` to `String`. Changed the name of
the `enable` parameter of `httpEnableTimelineLogging` to `enabled`.
3.0 | Added `isSocketProfilingAvailable`, `isHttpTimelineLoggingAvailable`,
and `isHttpProfilingAvailable` methods. Removed deprecated
`startSocketProfiling`, `pauseSocketProfiling`, `getHttpEnableTimelineLogging`,
and `setHttpEnableTimelineLogging` methods.
4.0 | Made the `updatedSince` parameter of `getHttpProfile` require the time to
be represented in microseconds since the "Unix epoch" instead of as a timestamp
on the monotonic clock used by the timeline. Made the `timestamp` property of
`HttpProfile` represent time in microseconds since the "Unix epoch" instead of
as a timestamp on the monotonic clock used by the timeline. Added `events`
property to `@HttpProfileRequest` and `HttpProfileRequest`. Made the `startTime`
and `endTime` properties of `@HttpProfileRequest` and `HttpProfileRequest`
represent time in microseconds since the "Unix epoch" instead of as timestamps
on the monotonic clock used by the timeline. Removed the `events` and `method`
properties from `HttpProfileRequestData`. Made the `cookies` property of
`HttpProfileRequestData` optional. Made the `startTime` and `endTime` properties
of `HttpProfileResponseData` represent time in microseconds since the "Unix
epoch" instead of as timestamps on the monotonic clock used by the timeline.
Made the `cookies`, `headers`, `compressionState`, `reasonPhrase`, `isRedirect`,
`persistentConnection`, `contentLength`, `statusCode`, and `startTime`
properties of `HttpProfileResponseData` optional. Made the `isDirect` property
of `HttpProfileProxyData` optional. Made the `timestamp` property of
`HttpProfileRequestEvent` represent time in microseconds since the "Unix epoch"
instead of as a timestamp on the monotonic clock used by the timeline.

View file

@ -27,7 +27,7 @@ abstract final class HttpProfiler {
static String toJson(int? updatedSince) {
return json.encode({
'type': _kType,
'timestamp': Timeline.now,
'timestamp': DateTime.now().microsecondsSinceEpoch,
'requests': [
for (final request in _profile.values.where(
(e) {
@ -42,7 +42,7 @@ abstract final class HttpProfiler {
class _HttpProfileEvent {
_HttpProfileEvent(this.name, this.arguments);
final int timestamp = Timeline.now;
final int timestamp = DateTime.now().microsecondsSinceEpoch;
final String name;
final Map? arguments;
@ -66,7 +66,7 @@ class _HttpProfileData {
// to the timeline.
id = _timeline.pass().toString();
requestInProgress = true;
requestStartTimestamp = Timeline.now;
requestStartTimestamp = DateTime.now().microsecondsSinceEpoch;
_timeline.start('HTTP CLIENT $method', arguments: {
'method': method.toUpperCase(),
'uri': uri.toString(),
@ -119,7 +119,7 @@ class _HttpProfileData {
}) {
// TODO(bkonyi): include encoding?
requestInProgress = false;
requestEndTimestamp = Timeline.now;
requestEndTimestamp = DateTime.now().microsecondsSinceEpoch;
requestDetails = <String, dynamic>{
// TODO(bkonyi): consider exposing certificate information?
// 'certificate': response.certificate,
@ -176,7 +176,7 @@ class _HttpProfileData {
filterKey: 'HTTP/client',
);
responseStartTimestamp = Timeline.now;
responseStartTimestamp = DateTime.now().microsecondsSinceEpoch;
_responseTimeline.start(
'HTTP CLIENT response of $method',
arguments: {
@ -189,7 +189,7 @@ class _HttpProfileData {
void finishRequestWithError(String error) {
requestInProgress = false;
requestEndTimestamp = Timeline.now;
requestEndTimestamp = DateTime.now().microsecondsSinceEpoch;
requestError = error;
_timeline.finish(arguments: {
'error': error,
@ -199,7 +199,7 @@ class _HttpProfileData {
void finishResponse() {
responseInProgress = false;
responseEndTimestamp = Timeline.now;
responseEndTimestamp = DateTime.now().microsecondsSinceEpoch;
requestEvent('Content Download');
_responseTimeline.finish();
_updated();
@ -210,7 +210,7 @@ class _HttpProfileData {
// the response stream is listened to with `cancelOnError: false`.
if (!responseInProgress!) return;
responseInProgress = false;
responseEndTimestamp = Timeline.now;
responseEndTimestamp = DateTime.now().microsecondsSinceEpoch;
responseError = error;
_responseTimeline.finish(arguments: {
'error': error,
@ -230,13 +230,13 @@ class _HttpProfileData {
'isolateId': isolateId,
'method': method,
'uri': uri.toString(),
'events': <Map<String, dynamic>>[
for (final event in requestEvents) event.toJson(),
],
'startTime': requestStartTimestamp,
if (!requestInProgress) 'endTime': requestEndTimestamp,
if (!requestInProgress)
'request': {
'events': <Map<String, dynamic>>[
for (final event in requestEvents) event.toJson(),
],
if (proxyDetails != null) 'proxyDetails': proxyDetails!,
if (requestDetails != null) ...requestDetails!,
if (requestError != null) 'error': requestError,
@ -255,7 +255,7 @@ class _HttpProfileData {
};
}
void _updated() => _lastUpdateTime = Timeline.now;
void _updated() => _lastUpdateTime = DateTime.now().microsecondsSinceEpoch;
static final String isolateId = Service.getIsolateID(Isolate.current)!;

View file

@ -5,7 +5,7 @@
part of dart.io;
// TODO(bkonyi): refactor into io_resource_info.dart
const int _versionMajor = 3;
const int _versionMajor = 4;
const int _versionMinor = 0;
const String _tcpSocket = 'tcp';