Skip to content
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

Merged
merged 15 commits into from
Jul 29, 2020
Merged
23 changes: 0 additions & 23 deletions packages/devtools_app/lib/src/common_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,29 +174,6 @@ StatelessWidget stopRecordingButton({
);
}

/// Button to stop recording data.
///
/// * `paused`: Whether recording is in progress.
/// * `includeTextWidth`: The minimum width the button can be before the text is
/// omitted.
/// * `onPressed`: The callback to be called upon pressing the button.
StatelessWidget stopButton({
Key key,
@required bool paused,
double includeTextWidth,
@required VoidCallback onPressed,
}) {
return OutlineButton(
key: key,
onPressed: paused ? null : onPressed,
child: MaterialIconLabel(
Icons.stop,
'Stop',
includeTextWidth: includeTextWidth,
),
);
}

// TODO(kenz): make recording info its own stateful widget that handles
// listening to value notifiers and building info.
Widget recordingInfo({
Expand Down
166 changes: 86 additions & 80 deletions packages/devtools_app/lib/src/http/http_request_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.
///
Expand Down Expand Up @@ -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';
Copy link
Contributor

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.

Copy link
Member Author

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.

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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this key camelCase?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -276,10 +286,6 @@ class HttpRequestData {
}
}

int _getTimelineMicrosecondsSinceEpoch(TraceEvent event) {
return _timelineMicrosBase + event.timestampMicros;
}

@override
String toString() => '$method $name';
String toString() => '$method $uri';
}
Loading