Bug 1244227 - properly report throttled network timing; r?Honza
MozReview-Commit-ID: BCJLSRGS0vE
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -333,18 +333,16 @@ NetworkResponseListener.prototype = {
this.receivedData = "";
this.httpActivity.owner.addResponseContent(
response,
this.httpActivity.discardResponseBody
);
- this.httpActivity.channel = null;
- this.httpActivity.owner = null;
this.httpActivity = null;
this.sink = null;
this.inputStream = null;
this.converter = null;
this.request = null;
this.owner = null;
},
@@ -458,16 +456,23 @@ NetworkMonitor.prototype = {
0x804b000b: "STATUS_RESOLVED",
0x804b0007: "STATUS_CONNECTING_TO",
0x804b0004: "STATUS_CONNECTED_TO",
0x804b0005: "STATUS_SENDING_TO",
0x804b000a: "STATUS_WAITING_FOR",
0x804b0006: "STATUS_RECEIVING_FROM"
},
+ httpDownloadActivities: [
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
+ ],
+
// Network response bodies are piped through a buffer of the given size (in
// bytes).
responsePipeSegmentSize: null,
owner: null,
/**
* Whether to save the bodies of network requests and responses.
@@ -643,16 +648,54 @@ NetworkMonitor.prototype = {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
if (this._matchRequest(channel)) {
throttler.manageUpload(channel);
}
}
},
/**
+ * A helper function for observeActivity. This does whatever work
+ * is required by a particular http activity event. Arguments are
+ * the same as for observeActivity.
+ */
+ _dispatchActivity: function (httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData) {
+ let transCodes = this.httpTransactionCodes;
+
+ // Store the time information for this activity subtype.
+ if (activitySubtype in transCodes) {
+ let stage = transCodes[activitySubtype];
+ if (stage in httpActivity.timings) {
+ httpActivity.timings[stage].last = timestamp;
+ } else {
+ httpActivity.timings[stage] = {
+ first: timestamp,
+ last: timestamp,
+ };
+ }
+ }
+
+ switch (activitySubtype) {
+ case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
+ this._onRequestBodySent(httpActivity);
+ break;
+ case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
+ this._onResponseHeader(httpActivity, extraStringData);
+ break;
+ case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
+ this._onTransactionClose(httpActivity);
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
* Begin observing HTTP traffic that originates inside the current tab.
*
* @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
*
* @param nsIHttpChannel channel
* @param number activityType
* @param number activitySubtype
* @param number timestamp
@@ -691,43 +734,30 @@ NetworkMonitor.prototype = {
break;
}
}
if (!httpActivity) {
return;
}
- let transCodes = this.httpTransactionCodes;
-
- // Store the time information for this activity subtype.
- if (activitySubtype in transCodes) {
- let stage = transCodes[activitySubtype];
- if (stage in httpActivity.timings) {
- httpActivity.timings[stage].last = timestamp;
- } else {
- httpActivity.timings[stage] = {
- first: timestamp,
- last: timestamp,
- };
- }
- }
-
- switch (activitySubtype) {
- case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
- this._onRequestBodySent(httpActivity);
- break;
- case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
- this._onResponseHeader(httpActivity, extraStringData);
- break;
- case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
- this._onTransactionClose(httpActivity);
- break;
- default:
- break;
+ // If we're throttling, we must not report events as they arrive
+ // from platform, but instead let the throttler emit the events
+ // after some time has elapsed.
+ if (httpActivity.downloadThrottle &&
+ this.httpDownloadActivities.indexOf(activitySubtype) >= 0) {
+ let callback = this._dispatchActivity.bind(this);
+ httpActivity.downloadThrottle
+ .addActivityCallback(callback, httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData);
+ } else {
+ this._dispatchActivity(httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData);
}
}),
/**
* Check if a given network request should be logged by this network monitor
* instance based on the current filters.
*
* @private
--- a/devtools/shared/webconsole/throttle.js
+++ b/devtools/shared/webconsole/throttle.js
@@ -8,16 +8,20 @@
const {CC, Ci, Cu, Cc} = require("chrome");
const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
"nsIArrayBufferInputStream");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
+loader.lazyServiceGetter(this, "gActivityDistributor",
+ "@mozilla.org/network/http-activity-distributor;1",
+ "nsIHttpActivityDistributor");
+
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
/**
* Construct a new nsIStreamListener that buffers data and provides a
* method to notify another listener when data is available. This is
* used to throttle network data on a per-channel basis.
*
@@ -27,16 +31,18 @@ const {setTimeout} = Cu.import("resource
* @param {NetworkThrottleQueue} queue the NetworkThrottleQueue to
* which status changes should be reported
*/
function NetworkThrottleListener(queue) {
this.queue = queue;
this.pendingData = [];
this.pendingException = null;
this.offset = 0;
+ this.responseStarted = false;
+ this.activities = {};
}
NetworkThrottleListener.prototype = {
QueryInterface:
XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInterfaceRequestor,
Ci.nsISupports]),
/**
@@ -128,26 +134,94 @@ NetworkThrottleListener.prototype = {
if (bytesPermitted === count) {
this.pendingData.shift();
done = true;
} else {
this.pendingData[0].count -= bytesPermitted;
}
this.offset += bytesPermitted;
+ // Maybe our state has changed enough to emit an event.
+ this.maybeEmitEvents();
+
return {length: bytesPermitted, done};
},
/**
* Return the number of pending data requests available for this
* listener.
*/
pendingCount: function () {
return this.pendingData.length;
},
+
+ /**
+ * This is called when an http activity event is delivered. This
+ * object delays the event until the appropriate moment.
+ */
+ addActivityCallback: function (callback, httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData) {
+ let datum = {callback, httpActivity, channel, activityType,
+ activitySubtype, extraSizeData,
+ extraStringData};
+ this.activities[activitySubtype] = datum;
+
+ if (activitySubtype ===
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE) {
+ this.totalSize = extraSizeData;
+ }
+
+ this.maybeEmitEvents();
+ },
+
+ /**
+ * This is called for a download throttler when the latency timeout
+ * has ended.
+ */
+ responseStart: function () {
+ this.responseStarted = true;
+ this.maybeEmitEvents();
+ },
+
+ /**
+ * Check our internal state and emit any http activity events as
+ * needed. Note that we wait until both our internal state has
+ * changed and we've received the real http activity event from
+ * platform. This approach ensures we can both pass on the correct
+ * data from the original event, and update the reported time to be
+ * consistent with the delay we're introducing.
+ */
+ maybeEmitEvents: function () {
+ if (this.responseStarted) {
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START);
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER);
+ }
+
+ if (this.totalSize !== undefined && this.offset >= this.totalSize) {
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE);
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE);
+ }
+ },
+
+ /**
+ * Emit an event for |code|, if the appropriate entry in
+ * |activities| is defined.
+ */
+ maybeEmit: function (code) {
+ if (this.activities[code] !== undefined) {
+ let {callback, httpActivity, channel, activityType,
+ activitySubtype, extraSizeData,
+ extraStringData} = this.activities[code];
+ let now = Date.now() * 1000;
+ callback(httpActivity, channel, activityType, activitySubtype,
+ now, extraSizeData, extraStringData);
+ this.activities[code] = undefined;
+ }
+ },
};
/**
* Construct a new queue that can be used to throttle the network for
* a group of related network requests.
*
* meanBPS {Number} Mean bytes per second.
* maxBPS {Number} Maximum bytes per second.
@@ -176,16 +250,17 @@ NetworkThrottleQueue.prototype = {
},
/**
* A helper function that lets the indicating listener start sending
* data. This is called after the initial round trip time for the
* listener has elapsed.
*/
allowDataFrom: function (throttleListener) {
+ throttleListener.responseStart();
this.pendingRequests.delete(throttleListener);
const count = throttleListener.pendingCount();
for (let i = 0; i < count; ++i) {
this.downloadQueue.push(throttleListener);
}
this.pump();
},