Skip to content

Commit aa26535

Browse files
committed
feat: Use PerformanceTraceFormatter to give a summary of the trace.
Note: this will error until a chrome-devtools-frontend with this change https://chromiumdash.appspot.com/commit/62e1652b2bf044ec53a2ec212cf53b1a62bc357e is released on npm. Bug: 441265851
1 parent 732267d commit aa26535

File tree

9 files changed

+129
-65
lines changed

9 files changed

+129
-65
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"start": "npm run build && node build/src/index.js",
1616
"start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
1717
"test": "npm run build && npx puppeteer browsers install chrome@latest && PUPPETEER_CHROME_VERSION=latest node --test-force-exit --test 'build/tests/**/*.test.js'",
18+
"test:update-snapshots": "npm run build && npx puppeteer browsers install chrome@latest && PUPPETEER_CHROME_VERSION=latest node --test-force-exit --test --test-update-snapshots 'build/tests/**/*.test.js'",
1819
"test:only": "npm run build && npx puppeteer browsers install chrome@latest && PUPPETEER_CHROME_VERSION=latest node --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
1920
"test:only:no-build": "npx puppeteer browsers install chrome@latest && PUPPETEER_CHROME_VERSION=latest node --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
2021
"prepare": "node --experimental-strip-types scripts/prepare.ts"
@@ -47,7 +48,7 @@
4748
"@types/yargs": "^17.0.33",
4849
"@typescript-eslint/eslint-plugin": "^8.43.0",
4950
"@typescript-eslint/parser": "^8.43.0",
50-
"chrome-devtools-frontend": "1.0.1513662",
51+
"chrome-devtools-frontend": "1.0.1515446",
5152
"eslint": "^9.35.0",
5253
"globals": "^16.4.0",
5354
"gts": "^6.0.2",

scripts/post-build.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,42 @@ function main(): void {
6868
const i18nDir = path.join(BUILD_DIR, devtoolsFrontEndCorePath, 'i18n');
6969
fs.mkdirSync(i18nDir, {recursive: true});
7070
const i18nFile = path.join(i18nDir, 'i18n.js');
71-
const i18nContent = `export const i18n = {
72-
registerUIStrings: () => {},
73-
getLocalizedString: (_, str) => {
74-
// So that the string passed in gets output verbatim.
75-
return str;
76-
},
77-
lockedLazyString: () => {},
78-
getLazilyComputedLocalizedString: () => {}
79-
};`;
71+
const i18nContent = `
72+
export const i18n = {
73+
registerUIStrings: () => {},
74+
getLocalizedString: (_, str) => {
75+
// So that the string passed in gets output verbatim.
76+
return str;
77+
},
78+
lockedLazyString: () => {},
79+
getLazilyComputedLocalizedString: () => {},
80+
};
81+
82+
// TODO(jacktfranklin): once the DocumentLatency insight does not depend on
83+
// this method, we can remove this stub.
84+
export const TimeUtilities = {
85+
millisToString(x) {
86+
const separator = '\xA0';
87+
const formatter = new Intl.NumberFormat('en-US', {
88+
style: 'unit',
89+
unitDisplay: 'narrow',
90+
minimumFractionDigits: 0,
91+
maximumFractionDigits: 1,
92+
unit: 'millisecond',
93+
});
94+
95+
const parts = formatter.formatToParts(x);
96+
for (const part of parts) {
97+
if (part.type === 'literal') {
98+
if (part.value === ' ') {
99+
part.value = separator;
100+
}
101+
}
102+
}
103+
104+
return parts.map(part => part.value).join('');
105+
}
106+
};`;
80107
writeFile(i18nFile, i18nContent);
81108

82109
// Create codemirror.next mock.
@@ -94,7 +121,10 @@ function main(): void {
94121
const rootDir = path.join(BUILD_DIR, devtoolsFrontEndCorePath, 'root');
95122
fs.mkdirSync(rootDir, {recursive: true});
96123
const runtimeFile = path.join(rootDir, 'Runtime.js');
97-
const runtimeContent = 'export default {};';
124+
const runtimeContent = `
125+
export function getChromeVersion() { return ''; };
126+
export const hostConfig = {};
127+
`;
98128
writeFile(runtimeFile, runtimeContent);
99129

100130
// Update protocol_client to remove:

src/devtools.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
*/
66

77
type CSSInJS = string & {_tag: 'CSS-in-JS'};
8+
declare module '*.css.js' {
9+
const styles: CSSInJS;
10+
export default styles;
11+
}

src/trace-processing/parse.ts

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {PerformanceInsightFormatter} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js';
7+
import {PerformanceTraceFormatter} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js';
88
import * as TraceEngine from '../../node_modules/chrome-devtools-frontend/front_end/models/trace/trace.js';
99
import {logger} from '../logger.js';
10+
import {AgentFocus} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js';
1011

1112
const engine = TraceEngine.TraceModel.Model.createWithAllHandlers();
1213

@@ -62,38 +63,9 @@ export async function parseRawTraceBuffer(
6263
// TODO(jactkfranklin): move the formatters from DevTools to use here.
6364
// This is a very temporary helper to output some text from the tool call to aid development.
6465
export function insightOutput(result: TraceResult): string {
65-
const mainNavigationId =
66-
result.parsedTrace.data.Meta.mainFrameNavigations.at(0)?.args.data
67-
?.navigationId;
68-
if (!mainNavigationId) {
69-
return '';
70-
}
71-
72-
let text = '';
73-
const insightsForNav = result.insights.get(mainNavigationId);
74-
if (!insightsForNav) {
75-
text += 'No Performance insights were found for this trace.';
76-
return text;
77-
}
78-
79-
const failingInsightKeys = Object.keys(insightsForNav.model).filter(
80-
insightKey => {
81-
const key = insightKey as keyof TraceEngine.Insights.Types.InsightModels;
82-
const data = insightsForNav.model[key] ?? null;
83-
return data?.state === 'fail';
84-
},
85-
) as Array<keyof TraceEngine.Insights.Types.InsightModels>;
86-
logger(`Found failing Insight keys: ${failingInsightKeys.join(', ')}`);
87-
88-
for (const failingKey of failingInsightKeys) {
89-
const modelData = insightsForNav.model[failingKey];
90-
const formatter = new PerformanceInsightFormatter(
91-
result.parsedTrace,
92-
modelData,
93-
);
94-
95-
const output = formatter.formatInsight();
96-
text += `${output}\n`;
97-
}
98-
return text;
66+
const focus = AgentFocus.full(result.parsedTrace);
67+
const serializer = new TraceEngine.EventsSerializer.EventsSerializer();
68+
const formatter = new PerformanceTraceFormatter(focus, serializer);
69+
const output = formatter.formatTraceSummary();
70+
return output;
9971
}

tests/tools/input.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe('input', () => {
112112
});
113113
});
114114

115-
it.only('waits for stable DOM', async () => {
115+
it('waits for stable DOM', async () => {
116116
server.addHtmlRoute(
117117
'/unstable',
118118
html`
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
exports[`Trace parsing > can format results of a trace 1`] = `
2+
URL: https://web.dev/
3+
Bounds: {min: 122410994891, max: 122416385853}
4+
CPU throttling: none
5+
Network throttling: none
6+
Metrics (lab / observed):
7+
- LCP: 129 ms, event: (eventKey: r-6063, ts: 122411126100)
8+
- LCP breakdown:
9+
- TTFB: 7.9 ms, bounds: {min: 122410996889, max: 122411004828}
10+
- Load delay: 33.2 ms, bounds: {min: 122411004828, max: 122411037986}
11+
- Load duration: 14.7 ms, bounds: {min: 122411037986, max: 122411052690}
12+
- Render delay: 73.4 ms, bounds: {min: 122411052690, max: 122411126100}
13+
- CLS: 0.00
14+
Available insights:
15+
- insight name: LCPBreakdown
16+
description: Each [subpart has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
17+
relevant trace bounds: {min: 122410996889, max: 122411126100}
18+
example question: Help me optimize my LCP score
19+
example question: Which LCP phase was most problematic?
20+
example question: What can I do to reduce the LCP time for this page load?
21+
- insight name: LCPDiscovery
22+
description: Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)
23+
relevant trace bounds: {min: 122411004828, max: 122411055039}
24+
example question: Suggest fixes to reduce my LCP
25+
example question: What can I do to reduce my LCP discovery time?
26+
example question: Why is LCP discovery time important?
27+
- insight name: RenderBlocking
28+
description: Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) can move these network requests out of the critical path.
29+
relevant trace bounds: {min: 122411037528, max: 122411053852}
30+
example question: Show me the most impactful render blocking requests that I should focus on
31+
example question: How can I reduce the number of render blocking requests?
32+
- insight name: DocumentLatency
33+
description: Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.
34+
relevant trace bounds: {min: 122410998910, max: 122411043781}
35+
estimated metric savings: FCP 0 ms, LCP 0 ms
36+
estimated wasted bytes: 77.1 kB
37+
example question: How do I decrease the initial loading time of my page?
38+
example question: Did anything slow down the request for this document?
39+
- insight name: ThirdParties
40+
description: 3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page's content.
41+
relevant trace bounds: {min: 122411037881, max: 122416229595}
42+
example question: Which third parties are having the largest impact on my page performance?
43+
`;

tests/trace-processing/parse.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,40 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
import {describe, it} from 'node:test';
7-
import assert from 'assert';
7+
import assert from 'node:assert';
88
import {
99
insightOutput,
1010
parseRawTraceBuffer,
1111
} from '../../src/trace-processing/parse.js';
1212
import {loadTraceAsBuffer} from './fixtures/load.js';
1313

1414
describe('Trace parsing', async () => {
15+
it.snapshot.setResolveSnapshotPath(testPath => {
16+
// By default the snapshots go into the build directory, but we want them
17+
// in the tests/ directory.
18+
const correctPath = testPath?.replace('/build/tests', '/tests');
19+
return correctPath + '.snapshot';
20+
});
21+
22+
// The default serializer is JSON.stringify which outputs a very hard to read
23+
// snapshot. So we override it to one that shows new lines literally rather
24+
// than via `\n`.
25+
it.snapshot.setDefaultSnapshotSerializers([String]);
26+
1527
it('can parse a Uint8Array from Tracing.stop())', async () => {
1628
const rawData = loadTraceAsBuffer('basic-trace.json.gz');
1729
const result = await parseRawTraceBuffer(rawData);
1830
assert.ok(result?.parsedTrace);
1931
assert.ok(result?.insights);
2032
});
2133

22-
it('can format results of a trace', async () => {
34+
it('can format results of a trace', async t => {
2335
const rawData = loadTraceAsBuffer('web-dev-with-commit.json.gz');
2436
const result = await parseRawTraceBuffer(rawData);
2537
assert.ok(result?.parsedTrace);
2638
assert.ok(result?.insights);
2739

2840
const output = insightOutput(result);
29-
assert.strictEqual(
30-
output.includes(
31-
'The Largest Contentful Paint (LCP) time for this navigation was 129.2 ms.',
32-
),
33-
true,
34-
);
35-
assert.strictEqual(
36-
output.includes('- fetchpriority=high should be applied: FAILED'),
37-
true,
38-
);
41+
t.assert.snapshot(output);
3942
});
4043
});

tsconfig.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,20 @@
2424
"node_modules/chrome-devtools-frontend/front_end/models/logs",
2525
"node_modules/chrome-devtools-frontend/front_end/models/text_utils",
2626
"node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator",
27+
"node_modules/chrome-devtools-frontend/front_end/models/crux-manager",
2728
"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts",
29+
"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts",
2830
"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts",
2931
"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.ts",
32+
"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance",
33+
"node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver",
34+
"node_modules/chrome-devtools-frontend/front_end/models/emulation",
35+
"node_modules/chrome-devtools-frontend/front_end/models/stack_trace",
36+
"node_modules/chrome-devtools-frontend/front_end/models/bindings",
37+
"node_modules/chrome-devtools-frontend/front_end/models/formatter",
38+
"node_modules/chrome-devtools-frontend/front_end/models/geometry",
39+
"node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes",
40+
"node_modules/chrome-devtools-frontend/front_end/models/workspace",
3041
"node_modules/chrome-devtools-frontend/front_end/core/common",
3142
"node_modules/chrome-devtools-frontend/front_end/core/sdk",
3243
"node_modules/chrome-devtools-frontend/front_end/core/protocol_client",

0 commit comments

Comments
 (0)