From 60833d9cd931daa4cb05fe18ff8b7f01223731b0 Mon Sep 17 00:00:00 2001 From: Derek Xu Date: Tue, 6 Feb 2024 19:26:06 +0000 Subject: [PATCH] [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 --- pkg/vm_service/CHANGELOG.md | 30 +- .../lib/src/dart_io_extensions.dart | 264 +++++++++--------- pkg/vm_service/pubspec.yaml | 2 +- .../test/get_http_profile_test.dart | 21 +- runtime/vm/service/service_extension.md | 134 +++++---- sdk/lib/_http/http_impl.dart | 24 +- sdk/lib/io/network_profiling.dart | 2 +- 7 files changed, 272 insertions(+), 205 deletions(-) diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md index 9d8038efcf2..1876d35d304 100644 --- a/pkg/vm_service/CHANGELOG.md +++ b/pkg/vm_service/CHANGELOG.md @@ -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. diff --git a/pkg/vm_service/lib/src/dart_io_extensions.dart b/pkg/vm_service/lib/src/dart_io_extensions.dart index e27d6b8a526..045f1ec4999 100644 --- a/pkg/vm_service/lib/src/dart_io_extensions.dart +++ b/pkg/vm_service/lib/src/dart_io_extensions.dart @@ -111,11 +111,12 @@ extension DartIOExtension on VmService { /// the specified time will be reported. Future 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 json) - : timestamp = json['timestamp'], - requests = (json['requests'] as List) + : timestamp = DateTime.fromMicrosecondsSinceEpoch(json['timestamp']!), + requests = json['requests']! .cast>() .map((e) => HttpProfileRequest._fromJson(e)) + .cast() .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 requests; @@ -347,12 +349,19 @@ class HttpProfileRequestRef { json == null ? null : HttpProfileRequestRef._fromJson(json); HttpProfileRequestRef._fromJson(Map 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((e) => HttpProfileRequestEvent._fromJson(e)) + .cast() + .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 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 json) - : _headers = json['headers'], + : _headers = UnmodifiableMapView(json['headers'] ?? {}), _connectionInfo = UnmodifiableMapView(json['connectionInfo'] ?? {}), _contentLength = json['contentLength'], _cookies = UnmodifiableListView(json['cookies']?.cast() ?? []), _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((e) => HttpProfileRequestEvent._fromJson(e)) - .toList()); + _proxyDetails = HttpProfileProxyData.parse(json['proxyDetails']), + error = json['error']; HttpProfileRequestData.buildSuccessfulRequest({ - required Map headers, - required Map? connectionInfo, - required int contentLength, + Map? headers, + Map? connectionInfo, + int? contentLength, required List 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? get connectionInfo { - return _connectionInfo == null - ? null - : UnmodifiableMapView(_connectionInfo); - } - + /// + /// Throws [HttpProfileRequestError] if `hasError` is `true`. + Map? get connectionInfo => _returnIfNoError(_connectionInfo); final Map? _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 get cookies => _returnIfNoError(_cookies); - final List _cookies; - - /// Events that has occurred while issuing this HTTP request. - final List events; + /// Throws [HttpProfileRequestError] if `hasError` is `true`. + List? get cookies => _returnIfNoError(_cookies); + final List? _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 get headers => UnmodifiableMapView( - _returnIfNoError(_headers), - ); + /// Throws [HttpProfileRequestError] if `hasError` is `true`. + Map? get headers => _returnIfNoError(_headers); final Map? _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? field) { + T? _returnIfNoError(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? json) => json == null ? null : HttpProfileProxyData._fromJson(json); HttpProfileProxyData._fromJson(Map 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? json) => json == null ? null : HttpProfileRequestEvent._fromJson(json); HttpProfileRequestEvent._fromJson(Map 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? 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? 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 json) - : startTime = json['startTime']!, - endTime = json['endTime'], - headers = json['headers']!, - connectionInfo = json['connectionInfo']!, - contentLength = json['contentLength']!, - compressionState = json['compressionState']!, - cookies = UnmodifiableListView(json['cookies']!.cast()), + : 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() ?? []), 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> redirects; /// Cookies set by the server (from the 'set-cookie' header). - final List cookies; + final List? cookies; /// Information about the client connection. final Map? connectionInfo; - /// Returns the client response headers. - final Map headers; + /// The client response headers. + final Map? 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; diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml index bbec1c9b5fe..981bbe0eabf 100644 --- a/pkg/vm_service/pubspec.yaml +++ b/pkg/vm_service/pubspec.yaml @@ -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. diff --git a/pkg/vm_service/test/get_http_profile_test.dart b/pkg/vm_service/test/get_http_profile_test.dart index 8e2aeaeb574..a74d340dcdf 100644 --- a/pkg/vm_service/test/get_http_profile_test.dart +++ b/pkg/vm_service/test/get_http_profile_test.dart @@ -71,6 +71,10 @@ Uri randomlyAddRequestParams(Uri uri) { Future 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 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 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 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 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); } } } diff --git a/runtime/vm/service/service_extension.md b/runtime/vm/service/service_extension.md index 879adb61592..a0982964474 100644 --- a/runtime/vm/service/service_extension.md +++ b/runtime/vm/service/service_extension.md @@ -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 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 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[] redirects; // Cookies set by the server (from the 'set-cookie' header). - string[] cookies; + string[] cookies [optional]; // Information about the client connection. map connectionInfo [optional]; - // Returns the client response headers. - map headers; + // The client response headers. + map 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. diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart index e8b3fc4d9df..6090f6f78c6 100644 --- a/sdk/lib/_http/http_impl.dart +++ b/sdk/lib/_http/http_impl.dart @@ -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 = { // 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': >[ + for (final event in requestEvents) event.toJson(), + ], 'startTime': requestStartTimestamp, if (!requestInProgress) 'endTime': requestEndTimestamp, if (!requestInProgress) 'request': { - 'events': >[ - 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)!; diff --git a/sdk/lib/io/network_profiling.dart b/sdk/lib/io/network_profiling.dart index 70bf066cb5c..158accf83d1 100644 --- a/sdk/lib/io/network_profiling.dart +++ b/sdk/lib/io/network_profiling.dart @@ -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';