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

Inject Lighthouse metrics back into trace for viewing #1446

Merged
merged 4 commits into from
Feb 3, 2017

Conversation

paulirish
Copy link
Member

Fixes #616

Here's a preview of what this patch gives us:
image

Live example: https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=https://www.dropbox.com/s/wqzll3jjbdseooe/hypem.com_01-05-2017_7-33-42_PM-0.trace.json?dl=0

In short: the lighthouse metrics like "First meaningful paint", "Time to Interactive", etc are mocked out as User Timing measures, such that they can be viewed in the devtools timeline. The these user timing measures are added right to the trace, so if you saved it with --save-assets, your hostname.datetime.trace.json file has the good stuff.

Additionally, this opens up other tools like pwmetrics to consume the metrics easier without so much reinterpretation of the audit results. cc @denar90

@denar90
Copy link
Contributor

denar90 commented Jan 10, 2017

Nice API 😍 , it will make pwmetrics life easier. Do you wanna have it documented?

@wardpeet
Copy link
Collaborator

this would be so awesome! 🎉

@@ -0,0 +1,204 @@
/**
* @license
* Copyright 2016 Google Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017


const log = require('../../../lighthouse-core/lib/log.js');

class Metrics {
Copy link
Contributor

Choose a reason for hiding this comment

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

throw in a @fileoverview

name: 'First Contentful Paint',
id: 'ttfcp',
getTs: auditResults => {
const fmpExt = auditResults['first-meaningful-paint'].extendedInfo;
Copy link
Contributor

Choose a reason for hiding this comment

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

Everything coming from auditResults['first-meaningful-paint'] looks like a mistake. Do we stash several things in auditResults['first-meaningful-paint'].extendedInfo? There are a few others like that, but reading this code is non-obvious.

Copy link
Member Author

Choose a reason for hiding this comment

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

Essentially we have 3 actual audits in lighthouse that collect these metrics. And yeah we stash several key metrics within the extInfo of each of them.
So, yeah, AFAIK all this is correct and intended... any ideas on how it could look less odd?

Copy link
Contributor

Choose a reason for hiding this comment

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

was expecting all the first-* stuff to be together, but I guess that makes sense if they're coming from different audits.

name: 'Visually Complete 85%',
id: 'vc85',
getTs: auditResults => {
const siExt = auditResults['time-to-interactive'].extendedInfo;
Copy link
Contributor

Choose a reason for hiding this comment

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

auditResults['speed-index-metric']?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, check that all of these are right. Now I'm really confused :)

Copy link
Member Author

Choose a reason for hiding this comment

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

actually vc85 does actually get delivered on the TTI choo-choo train. :)
that does mean that siExt => ttiExt, so thanks for catching that!

},
{
name: 'Visually Complete 100%',
id: 'vc',
Copy link
Contributor

Choose a reason for hiding this comment

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

suggest : id: 'vc100' to be like the other

Copy link
Member Author

Choose a reason for hiding this comment

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

sounds good.
I had chosen 'vc' origianlly because WebPageTest uses vc for the same thing. OR SO I THOUGHT!. Looking again... i can't see "vc" anywhere, so yeah i'm happy to tweak the id.

const metricDfns = Metrics.metricsDefinitions;
const resolvedMetrics = [];
metricDfns.forEach(metric => {
// try/catch in case auditResults is missing an audit result
Copy link
Contributor

Choose a reason for hiding this comment

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

an -> in



/**
* Constructs performance.measure trace events, which have start/end events as follows:
Copy link
Contributor

Choose a reason for hiding this comment

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

is "ph: b", beginning? If so, maybe say being/end events instead of start/end.

Copy link
Member Author

Choose a reason for hiding this comment

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

actually "b" stands for "nestable start" :D
The trace event phases are kind of insane.

@@ -0,0 +1,85 @@
/**
* @license
* Copyright 2016 Google Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

reviewed!

const assert = require('assert');

const dbwTrace = require('../../fixtures/traces/dbw_tester.json');
const dbwResults = require('../../fixtures/dbw_tester-perf-results.json');
Copy link
Member

Choose a reason for hiding this comment

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

can we reuse an existing trace? We're already at 26MB of trace fixtures and growing :)

Copy link
Member Author

Choose a reason for hiding this comment

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

was hoping you'd ask that. :) In this case it's pretty tricky because we need mock results and mock trace from the exact same run. Currently we have no fixtures that satisfy that.

I attempted to update one of our sample.json's (yes we have two) but after 25 minutes i concluded the rabbit hole was a bit too deep for me. :/

});
});

describe('assetSaver prepareAssets', () => {
Copy link
Member

Choose a reason for hiding this comment

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

should probably be in asset-saver-test.js

@@ -0,0 +1,204 @@
/**
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 indicate progressive web metrics? pwmetrics-events.js? Or is the intention to keep expanding?

Copy link
Member Author

Choose a reason for hiding this comment

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

renamed. sg

* @param {!Array<!AuditResult>} auditResults
* @return {!Array<{ts: number, id: string, name: string}>} metrics to consider
*/
gatherMetrics(auditResults) {
Copy link
Member

Choose a reason for hiding this comment

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

make static? (or can use this._auditResults)

/**
* Getter for our navigationStart trace event
*/
get navigationStartEvt() {
Copy link
Member

Choose a reason for hiding this comment

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

make just getNavigationStartEvt() or something instead of a getter? Kind of confusing below when this property appears.

Could also be an internal function that the constructor calls, populating the this.navigationStartEvt property so the behavior doesn't change from this

evts = new Metrics(dbwTrace.traceEvents, dbwResults.audits).generateFakeEvents();

const metricsWithoutNavstart = Metrics.metricsDefinitions.length - 1;
assert.equal(evts.length, 2 * metricsWithoutNavstart, 'All expected fake events not created');
Copy link
Member

Choose a reason for hiding this comment

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

maybe a comment that the events come in pairs, hence the 2 *?

const definitionsWithoutEvents = Metrics.metricsDefinitions
.filter(metric => metric.id !== 'navstart')
.filter(metric => !evts.find(e => e.name === metric.name));
assert.deepEqual(definitionsWithoutEvents, [], 'metrics are missing fake events');
Copy link
Member

Choose a reason for hiding this comment

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

better to do assert.strictEqual(definitionsWithoutEvents.length, 0, 'metrics are missing fake events');?


const eventsWithoutDefinitions = evts
.filter(evt => !Metrics.metricsDefinitions.find(metric => metric.name === evt.name));
assert.deepEqual(eventsWithoutDefinitions, [], 'fake events without a metric definition');
Copy link
Member

Choose a reason for hiding this comment

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

same length thing

});

it('generates fake trace events that are valid', () => {
const vizCompleteEvts = evts.filter(e => e.name.includes('Visually Complete 100'));
Copy link
Member

Choose a reason for hiding this comment

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

ideally we could avoid dependence on test ordering like this (I'd like to run our tests in a random order at some point, though we do this sort of thing in a few places in current tests :). Just re-run const evts = new Metrics(dbwTrace.traceEvents, dbwResults.audits).generateFakeEvents()? This test doesn't trigger the > 50ms warning on Travis, so should still be fast.

Copy link
Member Author

Choose a reason for hiding this comment

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

sgtm

@@ -119,6 +120,10 @@ function prepareAssets(artifacts, results) {
const traceData = Object.assign({}, trace);
const html = screenshotDump(screenshots, results);

if (results && results.audits) {
const evts = new Metrics(traceData.traceEvents, results.audits).generateFakeEvents();
traceData.traceEvents.push(...evts);
Copy link
Member

Choose a reason for hiding this comment

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

It seems bad to alter the original trace. Not sure when we did const traceData = Object.assign({}, trace);, but really we should be doing something like traceData.traceEvents = Array.from(traceData.traceEvents) to get a new array. Is that crazy?

Copy link
Member Author

Choose a reason for hiding this comment

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

On one hand, yeah, it feels bad to alter the trace. It'd be nice to keep the objects separate.

On the other:

  • I don't think we have a need to keep around the original trace.
  • We've sorted the trace in-place already (a few times).
  • The memory consumption is pretty huge for these traces, so there's that advantage to augmenting the solo trace.

Copy link
Member

Choose a reason for hiding this comment

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

We've sorted the trace in-place already (a few times).

very good point :)

@denar90
Copy link
Contributor

denar90 commented Jan 28, 2017

@paulirish do you need some help for finishing this?

@paulirish
Copy link
Member Author

PTAL

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

love it

(except
screen shot 2017-02-02 at 18 13 52
:P)

@brendankenny brendankenny merged commit ee8d967 into master Feb 3, 2017
@brendankenny brendankenny deleted the exportmetricstotrace branch February 3, 2017 02:14
@denar90
Copy link
Contributor

denar90 commented Feb 3, 2017

thanks guys 🎉

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

5 participants