Skip to content

Commit

Permalink
core(long-tasks): compute TBT impact (#15197)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine authored Oct 4, 2023
1 parent 0e0e48c commit 6539cfc
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 13 deletions.
9 changes: 8 additions & 1 deletion core/audits/long-tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {MainThreadTasks} from '../computed/main-thread-tasks.js';
import {PageDependencyGraph} from '../computed/page-dependency-graph.js';
import {LoadSimulator} from '../computed/load-simulator.js';
import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';

/** We don't always have timing data for short tasks, if we're missing timing data. Treat it as though it were 0ms. */
const DEFAULT_TIMING = {startTime: 0, endTime: 0, duration: 0};
Expand Down Expand Up @@ -66,8 +67,8 @@ class LongTasks extends Audit {
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
title: str_(UIStrings.title),
description: str_(UIStrings.description),
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
guidanceLevel: 1,
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
};
}

Expand Down Expand Up @@ -180,6 +181,9 @@ class LongTasks extends Audit {
const devtoolsLog = artifacts.devtoolsLogs[LongTasks.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLog, context);

const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
const tbtResult = await TotalBlockingTime.request(metricComputationData, context);

/** @type {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} */
let taskTimingsByEvent;

Expand Down Expand Up @@ -245,6 +249,9 @@ class LongTasks extends Audit {
notApplicable: results.length === 0,
details: tableDetails,
displayValue,
metricSavings: {
TBT: tbtResult.timing,
},
};
}
}
Expand Down
61 changes: 49 additions & 12 deletions core/test/audits/long-tasks-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import {readJson} from '../test-utils.js';
import LongTasks from '../../audits/long-tasks.js';
import {defaultSettings} from '../../config/constants.js';
import {createTestTrace} from '../create-test-trace.js';
import {networkRecordsToDevtoolsLog} from '../network-records-to-devtools-log.js';

Expand Down Expand Up @@ -52,36 +53,56 @@ function generateTraceWithLongTasks({count, duration = 200, withChildTasks = fal
return createTestTrace({
topLevelTasks: traceTasks,
timeOrigin: BASE_TS,
traceEnd: BASE_TS + 20_000,
});
}

describe('Long tasks audit', () => {
const devtoolsLog = networkRecordsToDevtoolsLog([{url: TASK_URL}]);
const devtoolsLog = networkRecordsToDevtoolsLog([{
url: TASK_URL,
priority: 'High',
}]);
const URL = {
requestedUrl: TASK_URL,
mainDocumentUrl: TASK_URL,
finalDisplayedUrl: TASK_URL,
};

it('should pass if there are no long tasks', async () => {
let context;

beforeEach(() => {
const settings = JSON.parse(JSON.stringify(defaultSettings));
settings.throttlingMethod = 'devtools';

context = {
computedCache: new Map(),
settings,
};
});

it('should pass and be non-applicable if there are no long tasks', async () => {
const artifacts = {
URL,
traces: {defaultPass: generateTraceWithLongTasks({count: 0})},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};
const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);
expect(result.details.items).toHaveLength(0);
expect(result.score).toBe(1);
expect(result.displayValue).toBeUndefined();
expect(result.notApplicable).toBeTruthy();
expect(result.metricSavings).toEqual({TBT: 0});
});

it('should return a list of long tasks with duration >= 50 ms', async () => {
const artifacts = {
URL,
traces: {defaultPass: generateTraceWithLongTasks({count: 4})},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};
const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);
expect(result.details.items).toMatchObject([
{url: 'Unattributable', duration: 200, startTime: 1000},
{url: 'Unattributable', duration: 200, startTime: 2000},
Expand All @@ -91,11 +112,13 @@ describe('Long tasks audit', () => {
expect(result.score).toBe(0);
expect(result.displayValue).toBeDisplayString('4 long tasks found');
expect(result.notApplicable).toBeFalsy();
expect(result.metricSavings).toEqual({TBT: 600}); // 4 * (200ms - 50ms)
});

it('should filter out tasks with duration less than 50 ms', async () => {
const trace = createTestTrace({
timeOrigin: BASE_TS,
traceEnd: BASE_TS + 20_000,
topLevelTasks: [
{ts: BASE_TS, duration: 1},
{ts: BASE_TS + 1000, duration: 30},
Expand All @@ -108,24 +131,29 @@ describe('Long tasks audit', () => {
URL,
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};

const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);
expect(result.details.items).toMatchObject([
{url: 'Unattributable', duration: 100, startTime: 2000},
{url: 'Unattributable', duration: 50, startTime: 4000},
]);
expect(result.score).toBe(0);
expect(result.displayValue).toBeDisplayString('2 long tasks found');
expect(result.metricSavings).toEqual({TBT: 50}); // (100ms - 50ms) + (50ms - 50ms)
});

it('should not filter out tasks with duration >= 50 ms only after throttling', async () => {
const artifacts = {
URL,
traces: {defaultPass: generateTraceWithLongTasks({count: 4, duration: 25})},
devtoolsLogs: {defaultPass: networkRecordsToDevtoolsLog([
{url: TASK_URL, timing: {connectEnd: 50, connectStart: 0.01, sslStart: 25, sslEnd: 40}},
])},
devtoolsLogs: {defaultPass: networkRecordsToDevtoolsLog([{
url: TASK_URL,
priority: 'High',
timing: {connectEnd: 50, connectStart: 0.01, sslStart: 25, sslEnd: 40},
}])},
GatherContext: {gatherMode: 'navigation'},
};
const context = {
computedCache: new Map(),
Expand All @@ -152,6 +180,7 @@ describe('Long tasks audit', () => {
expect(result.score).toBe(0);
expect(result.details.items).toHaveLength(4);
expect(result.displayValue).toBeDisplayString('4 long tasks found');
expect(result.metricSavings).toEqual({TBT: 200}); // 4 * (100ms - 50ms)
});

it('should populate url when tasks have an attributable url', async () => {
Expand All @@ -160,13 +189,15 @@ describe('Long tasks audit', () => {
URL,
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};
const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);
expect(result.details.items).toMatchObject([
{url: TASK_URL, duration: 300, startTime: 1000},
]);
expect(result.score).toBe(0);
expect(result.displayValue).toBeDisplayString('1 long task found');
expect(result.metricSavings).toEqual({TBT: 250}); // 300ms - 50ms
});

it('should include more than 20 tasks in debugData', async () => {
Expand All @@ -181,18 +212,21 @@ describe('Long tasks audit', () => {

const trace = createTestTrace({
timeOrigin: BASE_TS,
traceEnd: BASE_TS + 100_000,
topLevelTasks,
});
const artifacts = {
URL,
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};

const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);
expect(result.details.items).toHaveLength(20);
expect(result.score).toBe(0);
expect(result.displayValue).toBeDisplayString('20 long tasks found');
expect(result.metricSavings).toEqual({TBT: 249.99}); // (55ms - 50ms) * 50

const debugData = result.details.debugData;
expect(debugData).toMatchObject({
Expand All @@ -218,8 +252,9 @@ describe('Long tasks audit', () => {
URL,
traces: {defaultPass: trace},
devtoolsLogs: {defaultPass: devtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};
const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);

expect(result).toMatchObject({
score: 0,
Expand Down Expand Up @@ -250,8 +285,9 @@ describe('Long tasks audit', () => {
URL,
traces: {defaultPass: redirectTrace},
devtoolsLogs: {defaultPass: redirectDevtoolsLog},
GatherContext: {gatherMode: 'navigation'},
};
const result = await LongTasks.audit(artifacts, {computedCache: new Map()});
const result = await LongTasks.audit(artifacts, context);

expect(result).toMatchObject({
score: 0,
Expand All @@ -268,6 +304,7 @@ describe('Long tasks audit', () => {
}],
},
});
expect(result.metricSavings.TBT).toBeApproximately(353.53);

const debugData = result.details.debugData;
expect(debugData).toStrictEqual({
Expand Down

0 comments on commit 6539cfc

Please sign in to comment.