Skip to content

Conversation

@connorjclark
Copy link
Collaborator

split off from #14311

new time properties / renames

Renamed:

startTime            -> networkRequestTime
endTime              -> networkEndTime
responseReceivedTime -> responseHeadersEndTime

Added:

rendererStartTime
rendererEndTime 
rendererEndTime is not actually something we have yet (only exists in trace), so at the moment it's always equal to networkEndTime. It is included to fill out our model of the network, and in some places the former is used instead of the latter even though it doesn't make a difference yet. At the very least, it better documents intent.

startTime/endTime/responseReceivedTime have stuck around as aliases, as to keep this PR small (it'd be dozens of files with just renames) and focused.

excerpt from network request constructor:

/** When the renderer process initially discovers a network request, in milliseconds. */
this.rendererStartTime = -1;
/**
 * When the network service is about to handle a request, ie. just before going to the
 * HTTP cache or going to the network for DNS/connection setup, in milliseconds.
 */
this.networkRequestTime = -1;
/** When the last byte of the response headers is received, in milliseconds. */
this.responseHeadersEndTime = -1;
/** When the last byte of the response body is received, in milliseconds. */
this.networkEndTime = -1;
/**
 * Network response is done, this marks the time when the main thread is first available
 * to actually utilize the network resource, in milliseconds.
 *
 * TODO: we don't have this on the CDP, but it exists in trace as ResourceFinish.ts
 * So currently this is equivalent to networkEndTime.
 */
this.rendererEndTime = -1;

@connorjclark connorjclark requested a review from a team as a code owner December 1, 2022 00:02
@connorjclark connorjclark requested review from adamraine and removed request for a team December 1, 2022 00:02
}
}
// let timing;
// if (networkRecord.timing) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops, forgot to remove.

Note that this code has moved. I tried to explain in the comment elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

oops, forgot to remove

remove now?

Copy link

Choose a reason for hiding this comment

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

Kompitabel korleis type i dag

Comment on lines +67 to +71
rendererStartTime: normalizeTime(record.rendererStartTime),
networkRequestTime: normalizeTime(record.networkRequestTime),
responseHeadersEndTime: normalizeTime(record.responseHeadersEndTime),
networkEndTime: normalizeTime(record.networkEndTime),
rendererEndTime: normalizeTime(record.rendererEndTime),
Copy link
Contributor

Choose a reason for hiding this comment

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

adding the extra new properties could possibly wait until all the other audits are switched over to using them (where appropriate). That would keep those changes separate from the renames.

Copy link

Choose a reason for hiding this comment

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

Kom bedre å bare ta det litt av Kombef er litt for tynn Henrik

Comment on lines +198 to +200
// TODO: remove after timing refactor is done
// See https://github.com/GoogleChrome/lighthouse/pull/14311
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Easier just to s/startTime/networkRequestTime/g, s/endTime/networkEndTime/g and s/responseReceivedTime/responseHeadersEndTime/g? Seems unnecessary to have the codebase use both (and necessitates the network-records-to-devtools-log.js workaround) when there's very little review work for just a property rename.

Copy link
Member

Choose a reason for hiding this comment

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

I proposed that we do it this way.. i would prefer it cuz every since place where where we use startTime we should be very deliberate about which of the new timings we want.

And when I replicated the renames a bit ago, I caught a few cases that didn't make sense to me. :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The big reason for this approach is that this PR is just 7 files (less, if I take your suggestion to defer network requests audit changes)- but it would be dozens of files mixed in with the important changes here if done all at once.

The few lines in records to DevTools log is just encoding the framework we'd mostly use during the conversion process (next PR).

Copy link
Contributor

@brendankenny brendankenny Dec 1, 2022

Choose a reason for hiding this comment

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

I was probably too focused on an exact approach in my suggestion :) I think we're all broadly going for the same thing, my main issue was just anticipating a PR where some startTimes get changed to networkRequestTime and some get changed to rendererStartTime (and similar for endTime) and a reviewer will have to read each line carefully to double check which one was used.

I was attempting to suggest

  • a PR that only updates startTime to networkRequestTime (and endTime to networkEndTime) so no actual timing/test values changed
  • a PR that changes some of those networkRequestTimes to rendererStartTime (and networkEndTime to rendererEndTime), which would involve some timing/test value changes. That way the changes we want to be most deliberate about are highlighted (any property being changed is being switched to a new timing value).

If intermediate PRs before that point help break it up nicely, SGTM, I won't object to that :)

Copy link
Collaborator Author

@connorjclark connorjclark Dec 1, 2022

Choose a reason for hiding this comment

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

a PR that only updates startTime to networkRequestTime (and endTime to networkEndTime) so no actual timing/test values changed

Doing this next makes sense for the reasons you highlighted. Will do that. Hopefully it'll be the fastest on these PRs!

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.

i like it

const networkStartTimeTs = Number.isFinite(earliestStartTime) ?
earliestStartTime * 1000 : undefined;
const networkStartTimeTs = Number.isFinite(earliestMainThreadStartTime) ?
earliestMainThreadStartTime * 1000 : undefined;
Copy link
Member

Choose a reason for hiding this comment

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

i'm not sure about this * 1000, given that rendererstartime should be in ms.

but... this is a problem for @brendankenny as our debugData consumer :)

Copy link
Contributor

Choose a reason for hiding this comment

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

i'm not sure about this * 1000, given that rendererstartime should be in ms.

Yeah, I guess it's debatable what it should use. My original thinking was it should use microseconds because you compare it against observedNavigationStartTs to get the offset between CPU task timestamps and network times:

(CAST(JSON_VALUE(lighthouse, '$.audits."network-requests".details.debugData.networkStartTimeTs') AS FLOAT64) - CAST(JSON_VALUE(lighthouse, '$.audits.metrics.details.items[0].observedNavigationStartTs') AS FLOAT64)) / 1000 AS network_ts_offset

I put the Ts at the end of networkStartTimeTs to try to communicate that, but it's also a good argument that it's simpler if this audit only had values in ms 🤷

Comment on lines +198 to +200
// TODO: remove after timing refactor is done
// See https://github.com/GoogleChrome/lighthouse/pull/14311
/**
Copy link
Member

Choose a reason for hiding this comment

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

I proposed that we do it this way.. i would prefer it cuz every since place where where we use startTime we should be very deliberate about which of the new timings we want.

And when I replicated the renames a bit ago, I caught a few cases that didn't make sense to me. :)

Comment on lines 71 to 73
// Note: should use rendererEndTime but that is currently a "dummy" value equivalent
// to networkEndTime, and our networkRecordsToDevtoolsLog has no ability to roundtrip
// that.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean the test should be using rendererEndTime instead of networkEndTime or in addition to it? Seems like it should be in addition to, which is really a TODO to add tests when we add rendererEndTime support?

Copy link
Contributor

Choose a reason for hiding this comment

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

test looks good, can remove/update the comment?

Comment on lines 74 to 75
{url: 'https://example.com/0', rendererStartTime: 15.0, networkEndTime: 15.5},
{url: 'https://example.com/1', rendererStartTime: 15.5, networkEndTime: -1},
Copy link
Contributor

Choose a reason for hiding this comment

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

add networkRequestTime to these as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done.

but why is the second item here considered finished?

const willNeedTimingObject =
(netReqTime !== undefined && netReqTime !== networkRecord.rendererStartTime) ||
(networkRecord.responseHeadersEndTime !== undefined);
if (willNeedTimingObject) networkRecord.timing = networkRecord.timing || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

timing defaults to undefined, which is what audits depending on it check before accessing (e.g. uses-rel-preconnect), and the falsy check below will currently always be true

Copy link
Collaborator Author

@connorjclark connorjclark Dec 1, 2022

Choose a reason for hiding this comment

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

ugh, the problem here is that networkRequestTime isn't properly set when timing is missing, failing 8 tests in network-request-test.js.

For example, take update fetch stats for Lightrider test. the startTime/networkRequestTime value is now 50 instead of 0, because I wanted some separation between the first two timing properties. Without a timing object, such a distinction can never be made. So to roundtrip correctly I had to create the object here.

Should I 1) fix the tests to provide the relevant timing object to make the values come out right or 2) do a check in this records->log conversion if these properties differ, and only create a timing object magically...to reduce some test boilerplate. Leaning towards 1.

Copy link
Contributor

Choose a reason for hiding this comment

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

The goal of network-records-to-devtools-log is to take whatever super-partial network record you give it and make a plausible network record out of it, and if you want a more specific value somewhere in the generated record, you just have to pass it in. So we should go for 2 if possible. Having rendererStartTime === networkRequestTime seems ok if only one is provided?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe it would be easier to always create a timing object now?

Copy link
Collaborator Author

@connorjclark connorjclark Dec 1, 2022

Choose a reason for hiding this comment

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

fwiw for current PR code, willNeedTimingObject is always false for uses-rel-preconnect ... and there are zero tests in that audit that exercise a missing timing object. Is this a valid scenario?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

bump for the above question

Copy link
Contributor

Choose a reason for hiding this comment

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

fwiw for current PR code, willNeedTimingObject is always false for uses-rel-preconnect ... and there are zero tests in that audit that exercise a missing timing object. Is this a valid scenario?

Maybe just for failed requests? I'm not sure if it's important, it's possible it's just inherited from super old SDK code, or it could handle some important case we're not thinking of :)

if (willNeedTimingObject) networkRecord.timing = networkRecord.timing || {};

if (networkRecord.timing) {
if (networkRecord.timing.requestTime === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

don't these both need to be set if one of them is going to be?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, but the idea here was to never override a provided timing object, in case a test defined one (even partially).

Maybe instead this should check if both these timing fields are undefined before assigning either?

networkRecord.responseHeadersEndTime = networkRecord.responseReceivedTime;
}

// Set timing.requestTime and timing.receiveHeadersEnd to be values that
Copy link
Collaborator Author

@connorjclark connorjclark Dec 1, 2022

Choose a reason for hiding this comment

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

I thought I could add a precondition here:

if (record.rendererStartTime === undefined ^ record.networkEndTime === undefined) {
        // eslint-disable-next-line max-len
        throw new Error('expected test record to define rendererStartTime and networkEndTime together, but never just one');
      }

(I tried asserting on either being missing but way more failed than I wanted to check)

But 10 tests threw errors because they mocked network records and only defined startTime (or endTime). Should I update those tests so we can ensure more realistic values here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

actually all are from uses-rel-preload, and all just set startTime leaving endTime undefined.

it'd be easy to set endTime to something in those tests (-1, I guess that is the implicit default getting used?)

or maybe assign endTime -1 if startTime is defined and endTime is missing on a test record object?

Copy link
Contributor

Choose a reason for hiding this comment

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

it's fine to update the tests, but we do want some solution in here for future test writers, either picking a reasonable value or cause some failure (e.g. in the roundtripping) so the test author knows something is wrong

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought this would work:

if (record.rendererStartTime === undefined) {
        record.rendererStartTime = 0;
      }
      if (record.networkEndTime === undefined) {
        record.networkEndTime = -1;
      }

But had complications getting it to work w/ the redirectResponseTimestamp kludge used elsewhere. I'm getting a bit off track here, so gonna forget about normalizing the test data for now.

function networkRecordsToDevtoolsLog(networkRecords, options = {}) {
// Clone test network records objects before potential modifications.
networkRecords = networkRecords.map(record => {
if (record.constructor === Object) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can remove this object literal check and the one below and tests still all pass. I believe only the 'should roundtrip a real devtools log properly' test actual passes in real NetworkRequest instances. It seems wrong to modify those though, hence the checks here.

Copy link
Contributor

Choose a reason for hiding this comment

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

ideally this is just needed for the property name transition, so this seems fine as a temporary measure

Comment on lines 71 to 73
// Note: should use rendererEndTime but that is currently a "dummy" value equivalent
// to networkEndTime, and our networkRecordsToDevtoolsLog has no ability to roundtrip
// that.
Copy link
Contributor

Choose a reason for hiding this comment

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

test looks good, can remove/update the comment?

}
}
// let timing;
// if (networkRecord.timing) {
Copy link
Contributor

Choose a reason for hiding this comment

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

oops, forgot to remove

remove now?

const willNeedTimingObject =
(netReqTime !== undefined && netReqTime !== record.rendererStartTime) ||
(record.responseHeadersEndTime !== undefined);
if (willNeedTimingObject) record.timing = record.timing || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

seems like this could still be up in getResponseReceivedEvent rather than modifying the record. Could move back after the startTime/endTime gets removed

Copy link
Collaborator Author

@connorjclark connorjclark Dec 14, 2022

Choose a reason for hiding this comment

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

Ah, I recall why I did it this way. See the test failure for yarn mocha gatherers/inspector-issues:

      "sessionId": undefined,
      "statusCode": -1,
-     "timing": undefined,
+     "timing": Object {
+       "receiveHeadersEnd": 0,
+       "requestTime": -0.001,
+     },
      "transferSize": 0,
      "url": "https://example.com",
    },
  ]
      at networkRecordsToDevtoolsLog (file:///Users/cjamcl/src/lighthouse/core/test/network-records-to-devtools-log.js:295:40)
      at Context.<anonymous> (file:///Users/cjamcl/src/lighthouse/core/test/gather/gatherers/inspector-issues-test.js:329:19)
      at processImmediate (node:internal/timers:466:21)

Since timing object presence is now being automatically forced, the roundtrip assertion trips up unless we do the modification on the (cloned) test record.

So uh.... should I put it back in networkRecordsToDevtoolsLog?

Copy link
Contributor

@brendankenny brendankenny Dec 16, 2022

Choose a reason for hiding this comment

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

Part of it is inspector-issues-test is weird: it's Object.assigning to a real NetworkRequest, then passing it through networkRecordsToDevtoolsLog (it could have just used the partial passed through networkRecordsToDevtoolsLog, but it looks like those lines came a long time after the initial mockRequest method was written).

But even fixing that, there's a deeper problem and I don't think we can get away with optionally adding timing anymore. If you add

diff --git a/core/test/network-records-to-devtools-log-test.js b/core/test/network-records-to-devtools-log-test.js
index d114d483b..dd176e057 100644
--- a/core/test/network-records-to-devtools-log-test.js
+++ b/core/test/network-records-to-devtools-log-test.js
@@ -20,6 +20,11 @@ describe('networkRecordsToDevtoolsLog', () => {
       {method: 'Network.dataReceived'},
       {method: 'Network.loadingFinished'},
     ]);
+
+    const roundTripRecords = NetworkRecorder.recordsFromLogs(log);
+    const logNo2 = networkRecordsToDevtoolsLog(roundTripRecords);
+    const roundTripRecordsNo2 = NetworkRecorder.recordsFromLogs(logNo2);
+    expect(roundTripRecordsNo2).toMatchObject(roundTripRecords);
   });
 
   it('should set resource and transfer sizes', () => {

and run yarn mocha network-records-to-devtools-log this fails on this branch but passes on main. The first round-trip check (inside networkRecordsToDevtoolsLog) passes, but running the output through it again it fails on the same timing undefined vs Object part (the first time through it doesn't add a timing object, but the second time it does)

function networkRecordsToDevtoolsLog(networkRecords, options = {}) {
// Clone test network records objects before potential modifications.
networkRecords = networkRecords.map(record => {
if (record.constructor === Object) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ideally this is just needed for the property name transition, so this seems fine as a temporary measure

networkRecord.responseHeadersEndTime = networkRecord.responseReceivedTime;
}

// Set timing.requestTime and timing.receiveHeadersEnd to be values that
Copy link
Contributor

Choose a reason for hiding this comment

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

it's fine to update the tests, but we do want some solution in here for future test writers, either picking a reasonable value or cause some failure (e.g. in the roundtripping) so the test author knows something is wrong

const willNeedTimingObject =
(netReqTime !== undefined && netReqTime !== networkRecord.rendererStartTime) ||
(networkRecord.responseHeadersEndTime !== undefined);
if (willNeedTimingObject) networkRecord.timing = networkRecord.timing || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

fwiw for current PR code, willNeedTimingObject is always false for uses-rel-preconnect ... and there are zero tests in that audit that exercise a missing timing object. Is this a valid scenario?

Maybe just for failed requests? I'm not sure if it's important, it's possible it's just inherited from super old SDK code, or it could handle some important case we're not thinking of :)

@brendankenny
Copy link
Contributor

brendankenny commented Dec 7, 2022

also, FWIW, there's a network-records-to-devtools-log-test.js if that would be helpful to cement some of these corner cases 🤷

@connorjclark
Copy link
Collaborator Author

not sure why this PR is still open, I think we did all we wanted to in separate PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants