Skip to content

Commit cfc7625

Browse files
AlinaVarkkiDevtools-frontend LUCI CQ
authored andcommitted
Agentize "Modern HTTP" Insight
As the insight details, provide the list of requests using a non-modern HTTP. Also editing the short description of the insight in the sidebar to be more descriptive. Bug: 425275238 Change-Id: I4425c41572cdb6afd5a8fbc9f90b680d59d8ad08 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6684930 Commit-Queue: Alina Varkki <[email protected]> Auto-Submit: Alina Varkki <[email protected]> Reviewed-by: Jack Franklin <[email protected]>
1 parent f8813bb commit cfc7625

File tree

9 files changed

+138
-24
lines changed

9 files changed

+138
-24
lines changed

front_end/models/ai_assistance/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ Once it's working, it's a good idea to manually experiment with the new Insight
5757

5858
## Debugging tips
5959

60-
To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. So you cannot use the `npm start` script in this repo to run against Chrome for Testing. You'll want to run Canary but point at your local DevTools:
60+
To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. You can use the `npm start` with a canary flag in this repository to run against Chromium, or by running Chrome Canary and pointing it to your local DevTools.":
61+
62+
```
63+
npm start -- --browser=canary
64+
```
65+
66+
or
6167

6268
```
6369
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --custom-devtools-frontend=file://$(realpath out/Fast/gen/front_end)

front_end/models/ai_assistance/agents/PerformanceInsightsAgent.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ export class InsightContext extends ConversationContext<TimelineUtils.InsightAIC
194194
case 'Viewport':
195195
return [{title: 'How do I make sure my page is optimized for mobile viewing?'}];
196196
case 'ModernHTTP':
197-
return [{title: 'Is my site being served using the recommended HTTP best practices?'}];
197+
return [
198+
{title: 'Is my site being served using the recommended HTTP best practices?'},
199+
{title: 'Which resources are not using a modern HTTP protocol?'},
200+
];
198201
case 'LegacyJavaScript':
199202
return [{title: 'Is my site polyfilling modern JavaScript features?'}];
200203
default:

front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,70 @@ The longest interaction on the page was a \`click\` which had a total duration o
319319
});
320320
});
321321

322+
describe('ModernHTTP', () => {
323+
it('serializes the correct details when no requests are using legacy http', async function() {
324+
const {parsedTrace, insights} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
325+
assert.isOk(insights);
326+
const firstNav = getFirstOrError(parsedTrace.Meta.navigationsByNavigationId.values());
327+
const insight = getInsightOrError('ModernHTTP', insights, firstNav);
328+
const formatter = new PerformanceInsightFormatter(new ActiveInsight(insight, parsedTrace));
329+
const output = formatter.formatInsight();
330+
331+
const expected = `## Insight Title: Modern HTTP
332+
333+
## Insight Summary:
334+
Modern HTTP protocols, such as HTTP/2, are more efficient than older versions like HTTP/1.1 because they allow for multiple requests and responses to be sent over a single network connection, significantly improving page load performance by reducing latency and overhead. This insight identifies requests that can be upgraded to a modern HTTP protocol.
335+
336+
We apply a conservative approach when flagging HTTP/1.1 usage. This insight will only flag requests that meet all of the following criteria:
337+
1. Were served over HTTP/1.1 or an earlier protocol.
338+
2. Originate from an origin that serves at least 6 static asset requests, as the benefits of multiplexing are less significant with fewer requests.
339+
3. Are not served from 'localhost' or coming from a third-party source, where developers have no control over the server's protocol.
340+
341+
To pass this insight, ensure your server supports and prioritizes a modern HTTP protocol (like HTTP/2) for static assets, especially when serving a substantial number of them.
342+
343+
## Detailed analysis:
344+
There are no requests that were served over a legacy HTTP protocol.
345+
346+
## External resources:
347+
- https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2`;
348+
assert.strictEqual(output.trim(), expected.trim());
349+
});
350+
351+
it('serializes the correct details when requests are using legacy http', async function() {
352+
const {parsedTrace, insights} = await TraceLoader.traceEngine(this, 'http1.1.json.gz');
353+
assert.isOk(insights);
354+
const firstNav = getFirstOrError(parsedTrace.Meta.navigationsByNavigationId.values());
355+
const insight = getInsightOrError('ModernHTTP', insights, firstNav);
356+
const formatter = new PerformanceInsightFormatter(new ActiveInsight(insight, parsedTrace));
357+
const output = formatter.formatInsight();
358+
359+
const requestDetails =
360+
insight.http1Requests
361+
.map(request => TraceEventFormatter.networkRequest(request, parsedTrace, {verbose: true}))
362+
.join('\n');
363+
364+
const expected = `## Insight Title: Modern HTTP
365+
366+
## Insight Summary:
367+
Modern HTTP protocols, such as HTTP/2, are more efficient than older versions like HTTP/1.1 because they allow for multiple requests and responses to be sent over a single network connection, significantly improving page load performance by reducing latency and overhead. This insight identifies requests that can be upgraded to a modern HTTP protocol.
368+
369+
We apply a conservative approach when flagging HTTP/1.1 usage. This insight will only flag requests that meet all of the following criteria:
370+
1. Were served over HTTP/1.1 or an earlier protocol.
371+
2. Originate from an origin that serves at least 6 static asset requests, as the benefits of multiplexing are less significant with fewer requests.
372+
3. Are not served from 'localhost' or coming from a third-party source, where developers have no control over the server's protocol.
373+
374+
To pass this insight, ensure your server supports and prioritizes a modern HTTP protocol (like HTTP/2) for static assets, especially when serving a substantial number of them.
375+
376+
## Detailed analysis:
377+
Here is a list of the network requests that were served over a legacy HTTP protocol:
378+
${requestDetails}
379+
380+
## External resources:
381+
- https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2`;
382+
assert.strictEqual(output.trim(), expected.trim());
383+
});
384+
});
385+
322386
describe('Formatting TraceEvents', () => {
323387
it('formats network requests that have redirects', async function() {
324388
const {parsedTrace} = await TraceLoader.traceEngine(this, 'bad-document-request-latency.json.gz');
@@ -359,6 +423,7 @@ Initiator: https://chromedevtools.github.io/performance-stories/lcp-large-image/
359423
Redirects: no redirects
360424
Status code: 200
361425
MIME Type: text/css
426+
Protocol: unknown
362427
Priority: VeryHigh
363428
Render blocking: Yes
364429
From a service worker: No

front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,18 @@ Layout shifts in this cluster:
256256
${shiftsFormatted.join('\n')}`;
257257
}
258258

259+
if (Trace.Insights.Models.ModernHTTP.isModernHTTP(this.#insight)) {
260+
const requestSummary = this.#insight.http1Requests.map(
261+
request => TraceEventFormatter.networkRequest(request, this.#parsedTrace, {verbose: true}));
262+
263+
if (requestSummary.length === 0) {
264+
return 'There are no requests that were served over a legacy HTTP protocol.';
265+
}
266+
267+
return `Here is a list of the network requests that were served over a legacy HTTP protocol:
268+
${requestSummary.join('\n')}`;
269+
}
270+
259271
return '';
260272
}
261273

@@ -301,7 +313,7 @@ ${shiftsFormatted.join('\n')}`;
301313
case 'Cache':
302314
return '';
303315
case 'ModernHTTP':
304-
return '';
316+
return '- https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2';
305317
case 'LegacyJavaScript':
306318
return '';
307319
}
@@ -363,7 +375,14 @@ It is important that all of these checks pass to minimize the delay between the
363375
case 'Cache':
364376
return '';
365377
case 'ModernHTTP':
366-
return '';
378+
return `Modern HTTP protocols, such as HTTP/2, are more efficient than older versions like HTTP/1.1 because they allow for multiple requests and responses to be sent over a single network connection, significantly improving page load performance by reducing latency and overhead. This insight identifies requests that can be upgraded to a modern HTTP protocol.
379+
380+
We apply a conservative approach when flagging HTTP/1.1 usage. This insight will only flag requests that meet all of the following criteria:
381+
1. Were served over HTTP/1.1 or an earlier protocol.
382+
2. Originate from an origin that serves at least 6 static asset requests, as the benefits of multiplexing are less significant with fewer requests.
383+
3. Are not served from 'localhost' or coming from a third-party source, where developers have no control over the server's protocol.
384+
385+
To pass this insight, ensure your server supports and prioritizes a modern HTTP protocol (like HTTP/2) for static assets, especially when serving a substantial number of them.`;
367386
case 'LegacyJavaScript':
368387
return '';
369388
}
@@ -425,8 +444,17 @@ ${rootCauseText}`;
425444
static networkRequest(
426445
request: Trace.Types.Events.SyntheticNetworkRequest, parsedTrace: Trace.Handlers.Types.ParsedTrace,
427446
options: NetworkRequestFormatOptions): string {
428-
const {url, statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData} =
429-
request.args.data;
447+
const {
448+
url,
449+
statusCode,
450+
initialPriority,
451+
priority,
452+
fromServiceWorker,
453+
mimeType,
454+
responseHeaders,
455+
syntheticData,
456+
protocol
457+
} = request.args.data;
430458

431459
const titlePrefix = `## ${options.customTitle ?? 'Network request'}`;
432460

@@ -491,6 +519,7 @@ Durations:
491519
Redirects:${redirects.length ? '\n' + redirects.join('\n') : ' no redirects'}
492520
Status code: ${statusCode}
493521
MIME Type: ${mimeType}
522+
Protocol: ${protocol}
494523
${priorityLines.join('\n')}
495524
Render blocking: ${renderBlocking ? 'Yes' : 'No'}
496525
From a service worker: ${fromServiceWorker ? 'Yes' : 'No'}

front_end/models/trace/insights/ModernHTTP.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {getFirstOrError, getInsightOrError, processTrace} from '../../../testing
77
import * as Trace from '../trace.js';
88

99
import * as ModernHTTP from './ModernHTTP.js';
10-
const {determineNonHttp2Resources} = ModernHTTP;
10+
const {determineHttp1Requests: determineNonHttp2Resources} = ModernHTTP;
1111

1212
describeWithEnvironment('Cache', function() {
1313
describe('determineNonHttp2Resources', () => {
@@ -328,7 +328,7 @@ describeWithEnvironment('Cache', function() {
328328
const {data, insights} = await processTrace(this, 'http1.1.json.gz');
329329
const insight =
330330
getInsightOrError('ModernHTTP', insights, getFirstOrError(data.Meta.navigationsByNavigationId.values()));
331-
assert.deepEqual(insight.requests.map(r => r.args.data.url), [
331+
assert.deepEqual(insight.http1Requests.map(r => r.args.data.url), [
332332
'https://ads.jetpackdigital.com/sites/_uploads/1742278386bg_opt_640x350-avif.avif',
333333
'https://ads.jetpackdigital.com/sites/_uploads/1583540859Play.png',
334334
'https://ads.jetpackdigital.com/sites/_uploads/1583540859Muted.png',

front_end/models/trace/insights/ModernHTTP.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,21 @@ export const UIStrings = {
3939
/**
4040
* @description Text explaining that there were not requests that were slowed down by using HTTP/1.1. "HTTP/1.1" should not be translated.
4141
*/
42-
noOldProtocolRequests: 'No requests used HTTP/1.1'
42+
noOldProtocolRequests:
43+
'No requests used HTTP/1.1, or its current use of HTTP/1.1 does not present a significant optimization opportunity. HTTP/1.1 requests are only flagged if six or more static assets originate from the same origin, and they are not served from a local development environment or a third-party source.'
4344
} as const;
4445

4546
const str_ = i18n.i18n.registerUIStrings('models/trace/insights/ModernHTTP.ts', UIStrings);
4647
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
4748

4849
export type UseModernHTTPInsightModel = InsightModel<typeof UIStrings, {
49-
requests: Types.Events.SyntheticNetworkRequest[],
50+
http1Requests: Types.Events.SyntheticNetworkRequest[],
5051
}>;
5152

53+
export function isModernHTTP(model: InsightModel): model is UseModernHTTPInsightModel {
54+
return model.insightKey === InsightKeys.MODERN_HTTP;
55+
}
56+
5257
/**
5358
* Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
5459
* XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
@@ -99,10 +104,10 @@ function isMultiplexableStaticAsset(
99104
* [2] https://www.twilio.com/blog/2017/10/http2-issues.html
100105
* [3] https://www.cachefly.com/http-2-is-not-a-magic-bullet/
101106
*/
102-
export function determineNonHttp2Resources(
107+
export function determineHttp1Requests(
103108
requests: Types.Events.SyntheticNetworkRequest[], entityMappings: Handlers.Helpers.EntityMappings,
104109
firstPartyEntity: Handlers.Helpers.Entity|null): Types.Events.SyntheticNetworkRequest[] {
105-
const nonHttp2Resources: Types.Events.SyntheticNetworkRequest[] = [];
110+
const http1Requests: Types.Events.SyntheticNetworkRequest[] = [];
106111

107112
const groupedByOrigin = new Map<string, Types.Events.SyntheticNetworkRequest[]>();
108113
for (const record of requests) {
@@ -146,10 +151,10 @@ export function determineNonHttp2Resources(
146151
}
147152

148153
seenURLs.add(request.args.data.url);
149-
nonHttp2Resources.push(request);
154+
http1Requests.push(request);
150155
}
151156

152-
return nonHttp2Resources;
157+
return http1Requests;
153158
}
154159

155160
/**
@@ -193,12 +198,12 @@ function computeWasteWithGraph(
193198
}
194199

195200
function computeMetricSavings(
196-
nonHttp2Requests: Types.Events.SyntheticNetworkRequest[], context: InsightSetContext): MetricSavings|undefined {
201+
http1Requests: Types.Events.SyntheticNetworkRequest[], context: InsightSetContext): MetricSavings|undefined {
197202
if (!context.navigation || !context.lantern) {
198203
return;
199204
}
200205

201-
const urlsToChange = new Set(nonHttp2Requests.map(r => r.args.data.url));
206+
const urlsToChange = new Set(http1Requests.map(r => r.args.data.url));
202207

203208
const fcpGraph = context.lantern.metrics.firstContentfulPaint.optimisticGraph;
204209
const lcpGraph = context.lantern.metrics.largestContentfulPaint.optimisticGraph;
@@ -211,14 +216,14 @@ function computeMetricSavings(
211216

212217
function finalize(partialModel: PartialInsightModel<UseModernHTTPInsightModel>): UseModernHTTPInsightModel {
213218
return {
214-
insightKey: InsightKeys.IMAGE_DELIVERY,
219+
insightKey: InsightKeys.MODERN_HTTP,
215220
strings: UIStrings,
216221
title: i18nString(UIStrings.title),
217222
description: i18nString(UIStrings.description),
218223
category: InsightCategory.LCP,
219-
state: partialModel.requests.length > 0 ? 'fail' : 'pass',
224+
state: partialModel.http1Requests.length > 0 ? 'fail' : 'pass',
220225
...partialModel,
221-
relatedEvents: partialModel.requests,
226+
relatedEvents: partialModel.http1Requests,
222227
};
223228
}
224229

@@ -231,10 +236,10 @@ export function generateInsight(
231236
const entityMappings = parsedTrace.NetworkRequests.entityMappings;
232237
const firstPartyUrl = context.navigation?.args.data?.documentLoaderURL ?? parsedTrace.Meta.mainFrameURL;
233238
const firstPartyEntity = Handlers.Helpers.getEntityForUrl(firstPartyUrl, entityMappings.createdEntityCache);
234-
const nonHttp2Requests = determineNonHttp2Resources(contextRequests, entityMappings, firstPartyEntity ?? null);
239+
const http1Requests = determineHttp1Requests(contextRequests, entityMappings, firstPartyEntity ?? null);
235240

236241
return finalize({
237-
requests: nonHttp2Requests,
238-
metricSavings: computeMetricSavings(nonHttp2Requests, context),
242+
http1Requests,
243+
metricSavings: computeMetricSavings(http1Requests, context),
239244
});
240245
}

front_end/models/trace/insights/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,5 @@ export const enum InsightKeys {
147147
RENDER_BLOCKING = 'RenderBlocking',
148148
SLOW_CSS_SELECTOR = 'SlowCSSSelector',
149149
VIEWPORT = 'Viewport',
150+
MODERN_HTTP = 'ModernHTTP',
150151
}

front_end/panels/timeline/components/insights/ModernHTTP.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export class ModernHTTP extends BaseInsightComponent<UseModernHTTPInsightModel>
2121
static override readonly litTagName = Lit.StaticHtml.literal`devtools-performance-modern-http`;
2222
override internalName = 'modern-http';
2323

24+
protected override hasAskAiSupport(): boolean {
25+
return true;
26+
}
27+
2428
mapToRow(req: Trace.Types.Events.SyntheticNetworkRequest): TableDataRow {
2529
return {values: [eventRef(req), req.args.data.protocol], overlays: [this.#createOverlayForRequest(req)]};
2630
}
@@ -37,15 +41,15 @@ export class ModernHTTP extends BaseInsightComponent<UseModernHTTPInsightModel>
3741
}
3842

3943
override createOverlays(): Overlays.Overlays.TimelineOverlay[] {
40-
return this.model?.requests.map(req => this.#createOverlayForRequest(req)) ?? [];
44+
return this.model?.http1Requests.map(req => this.#createOverlayForRequest(req)) ?? [];
4145
}
4246

4347
override renderContent(): Lit.LitTemplate {
4448
if (!this.model) {
4549
return Lit.nothing;
4650
}
4751

48-
const rows = createLimitedRows(this.model.requests, this);
52+
const rows = createLimitedRows(this.model.http1Requests, this);
4953

5054
if (!rows.length) {
5155
return html`<div class="insight-section">${i18nString(UIStrings.noOldProtocolRequests)}</div>`;

front_end/ui/visual_logging/KnownContextValues.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,6 +3691,7 @@ export const knownContextValues = new Set([
36913691
'timeline.insight-ask-ai.lcp-by-phase',
36923692
'timeline.insight-ask-ai.lcp-discovery',
36933693
'timeline.insight-ask-ai.long-critical-network-tree',
3694+
'timeline.insight-ask-ai.modern-http',
36943695
'timeline.insight-ask-ai.render-blocking-requests',
36953696
'timeline.insight-ask-ai.slow-css-selector',
36963697
'timeline.insight-ask-ai.third-parties',

0 commit comments

Comments
 (0)