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

always construct networkRecords from devtoolsLog #2133

Merged
merged 5 commits into from
May 5, 2017

Conversation

brendankenny
Copy link
Member

@brendankenny brendankenny commented May 3, 2017

PR building on (and to merge into) #2062

Hopefully simplifies network and devtoolsLog recording. In total (with existing #2062 changes):

  • driver.networkRecorder was pulling triple duty, serving as network monitor, redirect monitor, and recorder of network activity for gatherers. Its replacement driver._networkStatus is only used for network events and is entirely internal to driver
  • devtoolsLog is taken for every pass
  • passData.networkRecords (passed into Gatherer.afterPass()) is constructed from the devtoolsLog for that pass. This eliminates questions of if network or devtoolsLog should start recording first and in what order, and it means that the networkRecords in afterPass() is the exact same as what audits get from the computed artifact. Downside is that networkRecords is reconstructed for every pass + possibly for computed artifacts, but this adds maybe 30-40ms total to a theverge.com run, so not a big deal.
  • enableUrlUpdateIfRedirected becomes a purely internal driver affair, as it no longer updates options.url directly, which lets driver worry about redirects and GatherRunner worry about options. Instead, driver.goToUrl() resolves on the final loaded (possibly redirected) URL. options.url is then set in GatherRunner.loadPage so that everything outside of that doesn't need to know anything changed.
  • beginTrace and beginDevtoolsLog are moved out of GatherRunner.loadPage to GatherRunner.pass as that makes a lot more sense and it matches the afterPass() location of endTrace and endDevtoolsLog (previously endNetworkCollect)

simplify devtoolsLog recording, url redirect tracking, and eliminate explicit network recording
Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

I really like these changes, moving towards a cleaner 🚗 + 🏃

* Used for monitoring network status events.
* @private {?NetworkRecorder}
*/
this._networkStatus = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not in love with this name change, network status makes me thing it's only watching for offline/online or something yet we listen for individual requests, dispatch all network messages to it, etc. Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, I just wanted to move away from the "records" part since we aren't using it for records. We're using it for busy, idle, and requestloaded events. Not sure what to call that :)

Copy link
Collaborator

@patrickhulce patrickhulce May 4, 2017

Choose a reason for hiding this comment

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

how about _networkStatusRecorder or _networkStatusMonitor?, just networkStatus makes me think it's literally a string 'offline' or something

Copy link
Member Author

Choose a reason for hiding this comment

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

ah, I see what you're saying. _networkStatusMonitor sounds great. @paulirish? (I can do the same thing for the begin/end methods)

this._networkStatus = new NetworkRecorder([], this);
this.enableUrlUpdateIfRedirected(options);

this.sendCommand('Network.enable').then(resolve, reject);
Copy link
Collaborator

Choose a reason for hiding this comment

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

can't we just return the result of this.sendCommand instead of wrapping in another promise?

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh it was pre-existing, might as well fix though :)

Copy link
Member Author

Choose a reason for hiding this comment

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

can't we just return the result of this.sendCommand instead of wrapping in another promise?

done

@@ -506,10 +543,17 @@ class Driver {
const maxWaitMs = (options.flags && options.flags.maxWaitForLoad) ||
Driver.MAX_WAIT_FOR_FULLY_LOADED;

return this.sendCommand('Page.enable')
const redirectTracking = {url};
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's hard to tell what this is for and why it's passed into _beginNetworkMontoring's options argument, maybe redirectTrackingOptions/Container/Something at least?

*/
enableUrlUpdateIfRedirected(opts) {
this._networkRecorder.on('requestloaded', redirectRequest => {
enableUrlUpdateIfRedirected(options) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's rename away from options now that it's not mutating the actual options object

Copy link
Member Author

Choose a reason for hiding this comment

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

done

* @return {!Promise}
* @private
*/
_beginNetworkMonitoring(options) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Copy link
Member Author

Choose a reason for hiding this comment

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

done

.then(_ => waitForLoad && this._waitForFullyLoaded(pauseAfterLoadMs, maxWaitMs))
.then(_ => {
this._endNetworkMonitoring();
return redirectTracking.url;
Copy link
Collaborator

Choose a reason for hiding this comment

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

since we're cleaning this up to not touch options it feels weird to still do this object mutation, what if it were restructured slightly so that we called _monitorUrlChanges() and could call getLatestUrl() at any point

Copy link
Member Author

Choose a reason for hiding this comment

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

went more or less with this, but only internal to driver. Didn't do a getLatestUrl() as driver is only semi-stateful, so it's not always clear that's what "latest" would be referring to, but got rid of all the options.url business. gotoURL still resolves on the final URL since during that method is the only time redirects are being tracked.

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

on the whole i like it. :)

if (!this._networkStatus) return;

const regexFilter = /^Network\./;
if (!regexFilter.test(method)) return;
Copy link
Member

Choose a reason for hiding this comment

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

while you're here can you change this to be free of regex:

if (!method.startsWith('Network.')) return;

my fault. thanks.

Copy link
Member Author

Choose a reason for hiding this comment

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

I just moved it into NetworkRecorder.dispatch (to mirror devtools-log.record), though we could arguably drop it altogether because the switch statement rejects anything not recognized

* @return {!Promise}
* @private
*/
_beginNetworkMonitoring(options) {
Copy link
Member

Choose a reason for hiding this comment

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

rename to beginNetworkIdleMonitoring since its for busy idle detection. not to be confused with devtoolsLog like stuff?

Copy link
Member Author

Choose a reason for hiding this comment

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

rename to beginNetworkIdleMonitoring since its for busy idle detection. not to be confused with devtoolsLog like stuff?

we also watch for requestloaded to watch for url redirect. Not sure if that matters :)

@brendankenny
Copy link
Member Author

fixed up and added a test that replays a wikipedia load to test redirect tracking. PTAL

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

lgtm % nit


// Update startingUrl if it's ever redirected.
this._monitoredUrl = startingUrl;
this._networkStatusMonitor.on('requestloaded', redirectRequest => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice this is so much more understandable 👍

@@ -155,6 +148,10 @@ class NetworkRecorder extends EventEmitter {
* @param {!Object<string, *>=} params
*/
dispatch(method, params) {
if (!/^Network\./.test(method)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I believe paul wanted startsWith instead

Copy link
Member Author

Choose a reason for hiding this comment

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

startsWith

done

* Construct network records from a log of devtools protocol messages. If a
* driver is provided, it will be used for request content lookups.
* @param {!DevtoolsLog} devtoolsLog
* @param {!Driver=} driver
Copy link
Collaborator

Choose a reason for hiding this comment

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

unrelated, but I still vote for a driver helper function instead, this is a really annoying artifact of having it be the method on network records :(

Copy link
Member Author

Choose a reason for hiding this comment

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

unrelated, but I still vote for a driver helper function instead, this is a really annoying artifact of having it be the method on network records :(

the problem is we need a live driver to get resource content, which will only be accessible in gatherers, but we need to create records from a devtools log during audits too (via a computed artifact) when there is no live driver. The first one argues for an instance method on driver, the second argues for a static method. So...it could be a static driver method, but still take a driver instance? But that seems like it has the same issue.

Not sure if there's a good solution :S

Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO, audits shouldn't need to live request content if gatherers didn't save them into a separate artifact meaning it can just be a method on a driver to getResourceContent and pass in a network record id, the construction of network records is not capable of reconstructing the content anyway

Copy link
Member Author

@brendankenny brendankenny May 4, 2017

Choose a reason for hiding this comment

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

IMO, audits shouldn't need to live request content if gatherers didn't save them into a separate artifact

right, it's not possible for audits to do that on current master already, as we throw away the driver by that point

meaning it can just be a method on a driver to getResourceContent and pass in a network record id, the construction of network records is not capable of reconstructing the content anyway

well I wish I had thought of that before I spent all that time reverse engineering that 'web-inspector' madness and then making it work again with this PR :)

I think Paul was going for the current approach because it makes accessing the content pretty seamless. We already use devtools' WebInspector.NetworkRequest objects, and they have requestContent on them, so it makes some sense to hook that method up similarly to how devtools does and have it send the command on your behalf.

It seems like we have a "this thing works here but doesn't work there" in both cases, so the choice would be:

  1. (current behavior) Support record.requestContent(). Meanwhile in audits throw on record.requestContent() (called when a driver wasn't used to initialize the network records) and document that it only works in audits, not gatherers
  2. If an audit wants to access content, they have to do driver.getRequestContent(record) or whatever. Meanwhile always throw on record.requestContent() and document driver method if you want content.

I do kind of like the explicitness of the second approach, making it crystal clear that you'll only be able to access content through the driver (so if you don't have a driver anymore, you won't get content). OTOH we're breaking the API of NetworkRequest and breaking away from the way devtools code does things. OTOOH probably no one would have known that record.requestContent existed without paul anyways and would have happily used a driver method (analogous to getObjectProperty).

@paulirish wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

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

also, it appears that all the other request*() methods on WebInspector.NetworkRequest don't send additional protocol commands, they already have what they need locally

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh let's pick 2, pick 2, pick 2, 😄 , can we @paulirish please? it's a bit silly to try to conform to the interface only here, when it makes the rest of the code more complicated especially when the assumptions of devtools are just very different than the assumptions in LH

Copy link
Member

Choose a reason for hiding this comment

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

ya cool let's pick 2.

make record.requestContent() throw and go back through protocol manually if you need responseBodies.

works for me.
do we need to change all that in this PR? would be nice not to..

Copy link
Collaborator

Choose a reason for hiding this comment

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

🎉

Copy link
Member Author

Choose a reason for hiding this comment

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

updated!

@@ -47,7 +47,7 @@ class ResponseCompression extends Gatherer {

if (!isContentEncoded) {
unoptimizedResponses.push({
record: record,
requestId: record.requestId,
Copy link
Member Author

@brendankenny brendankenny May 5, 2017

Choose a reason for hiding this comment

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

removed the full request record from the extendedInfo as it wasn't used and I assumed it would have problems with serialization of the report JSON? Or was that just the full set of network records?

Regardless, it wasn't used except to get requestId, so seemed fine to remove :)

Copy link
Member

Choose a reason for hiding this comment

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

ya good call. we shouldn't put records in extendedInfo

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

lets do it

getRequestContent(requestId) {
return this.sendCommand('Network.getResponseBody', {
requestId,
}).then(result => result.body);
Copy link
Member

Choose a reason for hiding this comment

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

add a comment in here that we're ignoring base64Encoded ?

// ignoring the result.base64Encoded boolean, which indicates if body is already encoded

Copy link
Member Author

Choose a reason for hiding this comment

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

💥

@brendankenny brendankenny merged commit fc03fc1 into netrecordsComputed May 5, 2017
@brendankenny brendankenny deleted the alldevtoolslogallthetime branch May 5, 2017 04:35
paulirish pushed a commit to paulirish/lighthouse that referenced this pull request May 5, 2017
simplify devtoolsLog recording, url redirect tracking, and eliminate explicit network recording
paulirish pushed a commit to paulirish/lighthouse that referenced this pull request May 5, 2017
simplify devtoolsLog recording, url redirect tracking, and eliminate explicit network recording
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants