-
Notifications
You must be signed in to change notification settings - Fork 337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add socket profiling to Network page #2191
Changes from 4 commits
850daf4
bace06c
9645dd6
24b6e8a
b3175ab
50e4411
a291d92
ebf1f51
21f0403
b253332
0d18d30
d1acbf2
47f3877
a902788
bef2ba1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,41 +2,11 @@ | |
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import '../network/network_model.dart'; | ||
import '../trace_event.dart'; | ||
import '../utils.dart'; | ||
import 'http.dart'; | ||
|
||
/// Contains all state relevant to completed and in-progress HTTP requests. | ||
class HttpRequests { | ||
HttpRequests({ | ||
this.requests = const [], | ||
this.invalidRequests = const [], | ||
this.outstandingRequests = const {}, | ||
}) : assert(requests != null), | ||
assert(invalidRequests != null), | ||
assert(outstandingRequests != null); | ||
|
||
/// A list of HTTP requests. | ||
/// | ||
/// Individual requests in this list can be either completed or in-progress. | ||
List<HttpRequestData> requests; | ||
|
||
/// A list of invalid HTTP requests received. | ||
/// | ||
/// These are requests that have completed but do not contain all the required | ||
/// information to display normally in the UI. | ||
List<HttpRequestData> invalidRequests; | ||
|
||
/// A mapping of timeline IDs to instances of HttpRequestData which are | ||
/// currently in-progress. | ||
Map<String, HttpRequestData> outstandingRequests; | ||
|
||
void clear() { | ||
requests.clear(); | ||
outstandingRequests.clear(); | ||
} | ||
} | ||
|
||
/// Used to represent an instant event emitted during an HTTP request. | ||
class HttpInstantEvent { | ||
HttpInstantEvent._(this._event); | ||
|
@@ -56,12 +26,12 @@ class HttpInstantEvent { | |
} | ||
|
||
/// An abstraction of an HTTP request made through dart:io. | ||
class HttpRequestData { | ||
class HttpRequestData extends NetworkRequest { | ||
HttpRequestData._( | ||
this._timelineMicrosBase, | ||
int timelineMicrosBase, | ||
this._startEvent, | ||
this._endEvent, | ||
); | ||
) : super(timelineMicrosBase); | ||
|
||
/// Build an instance from timeline events. | ||
/// | ||
|
@@ -99,17 +69,29 @@ class HttpRequestData { | |
return data; | ||
} | ||
|
||
final int _timelineMicrosBase; | ||
static const _connectionInfoKey = 'connectionInfo'; | ||
static const _contentTypeKey = 'content-type'; | ||
static const _cookieKey = 'cookie'; | ||
static const _errorKey = 'error'; | ||
static const _filterKey = 'filterKey'; | ||
static const _localPortKey = 'localPort'; | ||
static const _methodKey = 'method'; | ||
static const _requestHeadersKey = 'requestHeaders'; | ||
static const _responseHeadersKey = 'responseHeaders'; | ||
static const _statusCodeKey = 'statusCode'; | ||
static const _setCookieKey = 'set-cookie'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this key camelCase? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps add a TODO and file a bug upstream on the VM to cleanup the names. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a TODO and filed a bug. Names need to be changed upstream |
||
static const _uriKey = 'uri'; | ||
|
||
final TraceEvent _startEvent; | ||
TraceEvent _endEvent; | ||
|
||
// Do not add to this list directly! Call `_addInstantEvents` which is | ||
// responsible for calculating the time offsets of each event. | ||
final List<HttpInstantEvent> _instantEvents = []; | ||
|
||
/// The duration of the HTTP request, in milliseconds. | ||
@override | ||
Duration get duration { | ||
if (_endEvent == null || _startEvent == null) { | ||
if (inProgress || !isValid) { | ||
return null; | ||
} | ||
// Timestamps are in microseconds | ||
|
@@ -119,6 +101,21 @@ class HttpRequestData { | |
return range.duration; | ||
} | ||
|
||
@override | ||
String get contentType { | ||
if (responseHeaders == null || responseHeaders[_contentTypeKey] == null) { | ||
return null; | ||
} | ||
return responseHeaders[_contentTypeKey].toString(); | ||
} | ||
|
||
@override | ||
String get type { | ||
// TODO(kenz): pull in a package or implement functionality to pretty print | ||
// the MIME type from the 'content-type' field in a response header. | ||
return 'http'; | ||
} | ||
|
||
/// Whether the request is safe to display in the UI. | ||
/// | ||
/// It is possible to get invalid events if we receive an endEvent but no | ||
|
@@ -135,32 +132,38 @@ class HttpRequestData { | |
|
||
/// A map of general information associated with an HTTP request. | ||
Map<String, dynamic> get general { | ||
kenzieschmoll marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!isValid) return null; | ||
final copy = Map<String, dynamic>.from(_startEvent.args); | ||
if (_endEvent != null) { | ||
if (!inProgress) { | ||
copy.addAll(_endEvent.args); | ||
} | ||
copy.remove('requestHeaders'); | ||
copy.remove('responseHeaders'); | ||
copy.remove('filterKey'); | ||
copy.remove(_requestHeadersKey); | ||
copy.remove(_responseHeadersKey); | ||
copy.remove(_filterKey); | ||
return copy; | ||
} | ||
|
||
@override | ||
int get port { | ||
if (general == null) return null; | ||
final Map<String, dynamic> connectionInfo = general[_connectionInfoKey]; | ||
return connectionInfo != null ? connectionInfo[_localPortKey] : null; | ||
} | ||
|
||
/// True if the HTTP request hasn't completed yet, determined by the lack of | ||
/// an end event. | ||
bool get inProgress => _endEvent == null; | ||
|
||
/// All instant events logged to the timeline for this HTTP request. | ||
List<HttpInstantEvent> get instantEvents => _instantEvents; | ||
|
||
/// The HTTP method associated with this request. | ||
@override | ||
String get method { | ||
assert(_startEvent.args.containsKey('method')); | ||
return _startEvent.args['method']; | ||
if (!isValid) return null; | ||
assert(_startEvent.args.containsKey(_methodKey)); | ||
return _startEvent.args[_methodKey]; | ||
} | ||
|
||
/// The name of the request (currently the URI). | ||
String get name => uri.toString(); | ||
|
||
/// A list of all cookies contained within the request headers. | ||
List<Cookie> get requestCookies { | ||
// The request may still be in progress, in which case we don't display any | ||
|
@@ -169,27 +172,34 @@ class HttpRequestData { | |
if (headers == null) { | ||
return []; | ||
} | ||
return _parseCookies(headers['cookie'] ?? []); | ||
return _parseCookies(headers[_cookieKey] ?? []); | ||
} | ||
|
||
/// The request headers for the HTTP request. | ||
Map<String, dynamic> get requestHeaders { | ||
// The request may still be in progress, in which case we don't display any | ||
// headers. | ||
if (_endEvent == null) { | ||
return null; | ||
} | ||
return _endEvent.args['requestHeaders']; | ||
// headers, or the request may be invalid, in which case we also don't | ||
// display any headers. | ||
if (inProgress || !isValid) return null; | ||
return _endEvent.args[_requestHeadersKey]; | ||
} | ||
|
||
/// The time the HTTP request was issued. | ||
DateTime get requestTime { | ||
assert(_startEvent != null); | ||
@override | ||
DateTime get startTimestamp { | ||
if (!isValid) return null; | ||
return DateTime.fromMicrosecondsSinceEpoch( | ||
_getTimelineMicrosecondsSinceEpoch(_startEvent), | ||
timelineMicrosecondsSinceEpoch(_startEvent.timestampMicros), | ||
); | ||
} | ||
|
||
@override | ||
DateTime get endTimestamp { | ||
if (inProgress || !isValid) return null; | ||
return DateTime.fromMicrosecondsSinceEpoch( | ||
timelineMicrosecondsSinceEpoch(_endEvent.timestampMicros)); | ||
} | ||
|
||
/// A list of all cookies contained within the response headers. | ||
List<Cookie> get responseCookies { | ||
// The request may still be in progress, in which case we don't display any | ||
|
@@ -199,43 +209,43 @@ class HttpRequestData { | |
return []; | ||
} | ||
return _parseCookies( | ||
headers['set-cookie'] ?? [], | ||
headers[_setCookieKey] ?? [], | ||
); | ||
} | ||
|
||
/// The response headers for the HTTP request. | ||
Map<String, dynamic> get responseHeaders { | ||
// The request may still be in progress, in which case we don't display any | ||
// headers. | ||
if (_endEvent == null) { | ||
return null; | ||
} | ||
return _endEvent.args['responseHeaders']; | ||
// headers, or the request may be invalid, in which case we also don't | ||
// display any headers. | ||
if (inProgress || !isValid) return null; | ||
return _endEvent.args[_responseHeadersKey]; | ||
} | ||
|
||
/// A string representing the status of the request. | ||
/// | ||
/// If the request completed, this will be an HTTP status code. If an error | ||
/// was encountered, this will return 'Error'. | ||
@override | ||
String get status { | ||
if (inProgress || !isValid) return null; | ||
String statusCode; | ||
if (_endEvent != null) { | ||
final endArgs = _endEvent.args; | ||
if (endArgs.containsKey('error')) { | ||
// This case occurs when an exception has been thrown, so there's no | ||
// status code to associate with the request. | ||
statusCode = 'Error'; | ||
} else { | ||
statusCode = endArgs['statusCode'].toString(); | ||
} | ||
final endArgs = _endEvent.args; | ||
if (endArgs.containsKey(_errorKey)) { | ||
// This case occurs when an exception has been thrown, so there's no | ||
// status code to associate with the request. | ||
statusCode = 'Error'; | ||
} else { | ||
statusCode = endArgs[_statusCodeKey].toString(); | ||
} | ||
return statusCode; | ||
} | ||
|
||
/// The address the HTTP request was issued to. | ||
Uri get uri { | ||
assert(_startEvent.args.containsKey('uri')); | ||
return Uri.parse(_startEvent.args['uri']); | ||
@override | ||
String get uri { | ||
if (!isValid) return null; | ||
assert(_startEvent.args.containsKey(_uriKey)); | ||
return _startEvent.args[_uriKey]; | ||
} | ||
|
||
/// Merges the information from another [HttpRequestData] into this instance. | ||
|
@@ -276,10 +286,6 @@ class HttpRequestData { | |
} | ||
} | ||
|
||
int _getTimelineMicrosecondsSinceEpoch(TraceEvent event) { | ||
return _timelineMicrosBase + event.timestampMicros; | ||
} | ||
|
||
@override | ||
String toString() => '$method $name'; | ||
String toString() => '$method $uri'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: normalize the key names. This one has the word
Key
in the value but others don't.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filterKey is actually the name of the key in the arguments map. This would need to be changed on the VM but I think it is actually an accurate description as is because it is describing a key to group events on.