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

core(metrics): consumable metrics audit output #5101

Merged
merged 5 commits into from
May 4, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 87 additions & 49 deletions lighthouse-core/audits/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,66 +39,104 @@ class Metrics extends Audit {
const interactive = await artifacts.requestInteractive(metricComputationData);
const speedIndex = await artifacts.requestSpeedIndex(metricComputationData);
const estimatedInputLatency = await artifacts.requestEstimatedInputLatency(metricComputationData); // eslint-disable-line max-len
const metrics = [];

// Include the simulated/observed performance metrics
const metricsMap = {
firstContentfulPaint,
firstMeaningfulPaint,
firstCPUIdle,
interactive,
speedIndex,
estimatedInputLatency,
};
/** @type {UberMetricsItem} */
const metrics = {
// Include the simulated/observed performance metrics
firstContentfulPaint: firstContentfulPaint.timing,
firstContentfulPaintTs: firstContentfulPaint.timestamp,
firstMeaningfulPaint: firstMeaningfulPaint.timing,
firstMeaningfulPaintTs: firstMeaningfulPaint.timestamp,
firstCPUIdle: firstCPUIdle.timing,
firstCPUIdleTs: firstCPUIdle.timestamp,
interactive: interactive.timing,
interactiveTs: interactive.timestamp,
speedIndex: speedIndex.timing,
speedIndexTs: speedIndex.timestamp,
estimatedInputLatency: estimatedInputLatency.timing,
estimatedInputLatencyTs: estimatedInputLatency.timestamp,

for (const [metricName, values] of Object.entries(metricsMap)) {
metrics.push({
metricName,
timing: Math.round(values.timing),
timestamp: values.timestamp,
});
}
// Include all timestamps of interest from trace of tab
observedNavigationStart: traceOfTab.timings.navigationStart,
observedNavigationStartTs: traceOfTab.timestamps.navigationStart,
observedFirstPaint: traceOfTab.timings.firstPaint,
observedFirstPaintTs: traceOfTab.timestamps.firstPaint,
observedFirstContentfulPaint: traceOfTab.timings.firstContentfulPaint,
observedFirstContentfulPaintTs: traceOfTab.timestamps.firstContentfulPaint,
observedFirstMeaningfulPaint: traceOfTab.timings.firstMeaningfulPaint,
observedFirstMeaningfulPaintTs: traceOfTab.timestamps.firstMeaningfulPaint,
observedTraceEnd: traceOfTab.timings.traceEnd,
observedTraceEndTs: traceOfTab.timestamps.traceEnd,
observedLoad: traceOfTab.timings.load,
observedLoadTs: traceOfTab.timestamps.load,
observedDomContentLoaded: traceOfTab.timings.domContentLoaded,
observedDomContentLoadedTs: traceOfTab.timestamps.domContentLoaded,

// Include all timestamps of interest from trace of tab
const timingsEntries = /** @type {Array<[keyof LH.Artifacts.TraceTimes, number]>} */
(Object.entries(traceOfTab.timings));
for (const [traceEventName, timing] of timingsEntries) {
const uppercased = traceEventName.slice(0, 1).toUpperCase() + traceEventName.slice(1);
const metricName = `observed${uppercased}`;
const timestamp = traceOfTab.timestamps[traceEventName];
metrics.push({metricName, timing, timestamp});
}

// Include some visual metrics from speedline
metrics.push({
metricName: 'observedFirstVisualChange',
timing: speedline.first,
timestamp: (speedline.first + speedline.beginning) * 1000,
});
metrics.push({
metricName: 'observedLastVisualChange',
timing: speedline.complete,
timestamp: (speedline.complete + speedline.beginning) * 1000,
});
metrics.push({
metricName: 'observedSpeedIndex',
timing: speedline.speedIndex,
timestamp: (speedline.speedIndex + speedline.beginning) * 1000,
});
// Include some visual metrics from speedline
observedFirstVisualChange: speedline.first,
observedFirstVisualChangeTs: (speedline.first + speedline.beginning) * 1000,
observedLastVisualChange: speedline.complete,
observedLastVisualChangeTs: (speedline.complete + speedline.beginning) * 1000,
observedSpeedIndex: speedline.speedIndex,
observedSpeedIndexTs: (speedline.speedIndex + speedline.beginning) * 1000,
};

const headings = [
{key: 'metricName', itemType: 'text', text: 'Name'},
{key: 'timing', itemType: 'ms', granularity: 10, text: 'Value (ms)'},
];
for (const [name, value] of Object.entries(metrics)) {
const key = /** @type {keyof UberMetricsItem} */ (name);
if (typeof value === 'undefined') {
delete metrics[key];
Copy link
Member

Choose a reason for hiding this comment

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

maybe copy defined values to a new metrics object to return rather than delete from the original?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

any particular reason for this? I'll end up just creating an empty object with no type and casting at the end which seems sad 😢

Copy link
Member

Choose a reason for hiding this comment

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

I guess we can do it for now. delete is just almost always a bad idea :(

On the compiler side, this isn't really type checked either, since key has to be cast and value ends up as any. We just know that undefined and a number that can be rounded are the two possible cases. I don't know a great solution, though. We'd need an Object.entries() that doesn't widen, an Array.filter() that really filters types, and an Object.fromEntries() from TC39 :)

The other option is to leave them defined as undefined. Was that bad? They'll get dropped from the JSON and we're testing against undefined anyways

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 I guess it's not the end of the world to leave them undefined, will do

} else {
metrics[key] = Math.round(value);
}
}

const tableDetails = Audit.makeTableDetails(headings, metrics);
/** @type {MetricsDetails} */
const details = {items: [metrics]};

return {
score: 1,
rawValue: interactive.timing,
details: tableDetails,
details,
};
}
}

/**
* @typedef UberMetricsItem
* @property {number} firstContentfulPaint
* @property {number=} firstContentfulPaintTs
* @property {number} firstMeaningfulPaint
* @property {number=} firstMeaningfulPaintTs
* @property {number} firstCPUIdle
* @property {number=} firstCPUIdleTs
* @property {number} interactive
* @property {number=} interactiveTs
* @property {number} speedIndex
* @property {number=} speedIndexTs
* @property {number} estimatedInputLatency
* @property {number=} estimatedInputLatencyTs
* @property {number} observedNavigationStart
* @property {number} observedNavigationStartTs
* @property {number} observedFirstPaint
* @property {number} observedFirstPaintTs
* @property {number} observedFirstContentfulPaint
* @property {number} observedFirstContentfulPaintTs
* @property {number} observedFirstMeaningfulPaint
* @property {number} observedFirstMeaningfulPaintTs
* @property {number} observedTraceEnd
* @property {number} observedTraceEndTs
* @property {number} observedLoad
* @property {number} observedLoadTs
* @property {number} observedDomContentLoaded
* @property {number} observedDomContentLoadedTs
* @property {number} observedFirstVisualChange
* @property {number} observedFirstVisualChangeTs
* @property {number} observedLastVisualChange
* @property {number} observedLastVisualChangeTs
* @property {number} observedSpeedIndex
* @property {number} observedSpeedIndexTs
*/

/** @typedef {{items: [UberMetricsItem]}} MetricsDetails */

module.exports = Metrics;
52 changes: 27 additions & 25 deletions lighthouse-core/lib/traces/pwmetrics-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

const log = require('lighthouse-logger');

function findValueInMetricsAuditFn(metricName, timingOrTimestamp) {
// TODO: rework this file to not need this function
// see https://github.com/GoogleChrome/lighthouse/pull/5101/files#r186168840
function findValueInMetricsAuditFn(metricName) {
return auditResults => {
const metricsAudit = auditResults.metrics;
if (!metricsAudit || !metricsAudit.details || !metricsAudit.details.items) return;

const values = metricsAudit.details.items.find(item => item.metricName === metricName);
return values && values[timingOrTimestamp];
const values = metricsAudit.details.items[0];
return values && values[metricName];
Copy link
Member

Choose a reason for hiding this comment

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

this can be a TODO, but seems like we can just get rid of this whole findValueInMetricsAuditFn thing now. If there's no metrics audit, pwmetrics can't really do much, so it could check once at the beginning. Then all the gets are just property dereferencing with the new object layout.

(metricsDefinitions might still be useful, haven't looked into it)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sure TODO added

};
}

Expand All @@ -33,68 +35,68 @@ class Metrics {
{
name: 'Navigation Start',
id: 'navstart',
getTs: findValueInMetricsAuditFn('observedNavigationStart', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedNavigationStart', 'timing'),
getTs: findValueInMetricsAuditFn('observedNavigationStartTs'),
getTiming: findValueInMetricsAuditFn('observedNavigationStart'),
},
{
name: 'First Contentful Paint',
id: 'ttfcp',
getTs: findValueInMetricsAuditFn('observedFirstContentfulPaint', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedFirstContentfulPaint', 'timing'),
getTs: findValueInMetricsAuditFn('observedFirstContentfulPaintTs'),
getTiming: findValueInMetricsAuditFn('observedFirstContentfulPaint'),
},
{
name: 'First Meaningful Paint',
id: 'ttfmp',
getTs: findValueInMetricsAuditFn('observedFirstMeaningfulPaint', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedFirstMeaningfulPaint', 'timing'),
getTs: findValueInMetricsAuditFn('observedFirstMeaningfulPaintTs'),
getTiming: findValueInMetricsAuditFn('observedFirstMeaningfulPaint'),
},
{
name: 'Speed Index',
id: 'si',
getTs: findValueInMetricsAuditFn('observedSpeedIndex', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedSpeedIndex', 'timing'),
getTs: findValueInMetricsAuditFn('observedSpeedIndexTs'),
getTiming: findValueInMetricsAuditFn('observedSpeedIndex'),
},
{
name: 'First Visual Change',
id: 'fv',
getTs: findValueInMetricsAuditFn('observedFirstVisualChange', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedFirstVisualChange', 'timing'),
getTs: findValueInMetricsAuditFn('observedFirstVisualChangeTs'),
getTiming: findValueInMetricsAuditFn('observedFirstVisualChange'),
},
{
name: 'Visually Complete 100%',
id: 'vc100',
getTs: findValueInMetricsAuditFn('observedLastVisualChange', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedLastVisualChange', 'timing'),
getTs: findValueInMetricsAuditFn('observedLastVisualChangeTs'),
getTiming: findValueInMetricsAuditFn('observedLastVisualChange'),
},
{
name: 'First CPU Idle',
id: 'ttfi',
getTs: findValueInMetricsAuditFn('firstCPUIdle', 'timestamp'),
getTiming: findValueInMetricsAuditFn('firstCPUIdle', 'timing'),
getTs: findValueInMetricsAuditFn('firstCPUIdleTs'),
getTiming: findValueInMetricsAuditFn('firstCPUIdle'),
},
{
name: 'Interactive',
id: 'tti',
getTs: findValueInMetricsAuditFn('interactive', 'timestamp'),
getTiming: findValueInMetricsAuditFn('interactive', 'timing'),
getTs: findValueInMetricsAuditFn('interactiveTs'),
getTiming: findValueInMetricsAuditFn('interactive'),
},
{
name: 'End of Trace',
id: 'eot',
getTs: findValueInMetricsAuditFn('observedTraceEnd', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedTraceEnd', 'timing'),
getTs: findValueInMetricsAuditFn('observedTraceEndTs'),
getTiming: findValueInMetricsAuditFn('observedTraceEnd'),
},
{
name: 'On Load',
id: 'onload',
getTs: findValueInMetricsAuditFn('observedLoad', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedLoad', 'timing'),
getTs: findValueInMetricsAuditFn('observedLoadTs'),
getTiming: findValueInMetricsAuditFn('observedLoad'),
},
{
name: 'DOM Content Loaded',
id: 'dcl',
getTs: findValueInMetricsAuditFn('observedDomContentLoaded', 'timestamp'),
getTiming: findValueInMetricsAuditFn('observedDomContentLoaded', 'timing'),
getTs: findValueInMetricsAuditFn('observedDomContentLoadedTs'),
getTiming: findValueInMetricsAuditFn('observedDomContentLoaded'),
},
];
}
Expand Down
19 changes: 10 additions & 9 deletions lighthouse-core/test/audits/metrics-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,6 @@ describe('Performance: metrics', () => {
const result = await Audit.audit(artifacts, {settings});

assert.deepStrictEqual(result.details.items[0], {
metricName: 'firstContentfulPaint',
timing: 2038,
timestamp: undefined,
});

const metrics = {};
result.details.items.forEach(item => metrics[item.metricName] = Math.round(item.timing));

assert.deepStrictEqual(metrics, {
firstContentfulPaint: 2038,
firstMeaningfulPaint: 2851,
firstCPUIdle: 5308,
Expand All @@ -46,15 +37,25 @@ describe('Performance: metrics', () => {
estimatedInputLatency: 104,

observedNavigationStart: 0,
observedNavigationStartTs: 225414172015,
observedFirstPaint: 499,
observedFirstPaintTs: 225414670868,
observedFirstContentfulPaint: 499,
observedFirstContentfulPaintTs: 225414670885,
observedFirstMeaningfulPaint: 783,
observedFirstMeaningfulPaintTs: 225414955343,
observedTraceEnd: 12540,
observedTraceEndTs: 225426711887,
observedLoad: 2199,
observedLoadTs: 225416370913,
observedDomContentLoaded: 560,
observedDomContentLoadedTs: 225414732309,
observedFirstVisualChange: 520,
observedFirstVisualChangeTs: 225414692015,
observedLastVisualChange: 818,
observedLastVisualChangeTs: 225414990015,
observedSpeedIndex: 605,
observedSpeedIndexTs: 225414776724,
});
});
});
Loading