Skip to content

Commit e35e159

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
[AI] Support multiple insight sets in Performance agent
* Add the insight set id to the "getInsightDetails" function * Add a summary of each insight set to the trace formatter summary * Add a better error message when an insight name is invalid Fixed: 452335091 Change-Id: Ice54791f9d8c52a441b26633c103daab9ea670d7 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7087471 Reviewed-by: Paul Irish <[email protected]> Commit-Queue: Connor Clark <[email protected]>
1 parent 663d507 commit e35e159

File tree

4 files changed

+189
-57
lines changed

4 files changed

+189
-57
lines changed

front_end/models/ai_assistance/agents/PerformanceAgent.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -627,39 +627,55 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
627627
#declareFunctions(context: PerformanceTraceContext): void {
628628
const focus = context.getItem();
629629
const {parsedTrace} = focus;
630-
const insightSet = focus.primaryInsightSet;
631630

632-
this.declareFunction<{insightName: string}, {details: string}>('getInsightDetails', {
631+
this.declareFunction<{insightSetId: string, insightName: string}, {details: string}>('getInsightDetails', {
633632
description:
634-
'Returns detailed information about a specific insight. Use this before commenting on any specific issue to get more information.',
633+
'Returns detailed information about a specific insight of an insight set. Use this before commenting on any specific issue to get more information.',
635634
parameters: {
636635
type: Host.AidaClient.ParametersTypes.OBJECT,
637636
description: '',
638637
nullable: false,
639638
properties: {
639+
insightSetId: {
640+
type: Host.AidaClient.ParametersTypes.STRING,
641+
description:
642+
'The id for the specific insight set. Only use the ids given in the "Available insight sets" list.',
643+
nullable: false,
644+
},
640645
insightName: {
641646
type: Host.AidaClient.ParametersTypes.STRING,
642-
description: 'The name of the insight. Only use the insight names given in the Available Insights list.',
647+
description: 'The name of the insight. Only use the insight names given in the "Available insights" list.',
643648
nullable: false,
644649
}
645650
},
646651
},
647652
displayInfoFromArgs: params => {
648653
return {
649654
title: lockedString(`Investigating insight ${params.insightName}…`),
650-
action: `getInsightDetails('${params.insightName}')`
655+
action: `getInsightDetails('${params.insightSetId}', '${params.insightName}')`
651656
};
652657
},
653658
handler: async params => {
654659
debugLog('Function call: getInsightDetails', params);
660+
const insightSet = parsedTrace.insights?.get(params.insightSetId);
661+
if (!insightSet) {
662+
const valid = ([...parsedTrace.insights?.values() ?? []])
663+
.map(
664+
insightSet => `id: ${insightSet.id}, url: ${insightSet.url}, bounds: ${
665+
this.#formatter?.serializeBounds(insightSet.bounds)}`)
666+
.join('; ');
667+
return {error: `Invalid insight set id. Valid insight set ids are: ${valid}`};
668+
}
669+
655670
const insight = insightSet?.model[params.insightName as keyof Trace.Insights.Types.InsightModels];
656671
if (!insight) {
657-
return {error: 'No insight available'};
672+
const valid = Object.keys(insightSet?.model).join(', ');
673+
return {error: `No insight available. Valid insight names are: ${valid}`};
658674
}
659675

660676
const details = new PerformanceInsightFormatter(focus, insight).formatInsight();
661677

662-
const key = `getInsightDetails('${params.insightName}')`;
678+
const key = `getInsightDetails('${params.insightSetId}', '${params.insightName}')`;
663679
this.#cacheFunctionResult(focus, key, details);
664680
return {result: {details}};
665681
},

front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,18 @@ IMPORTANT: Never show eventKey to the user.
504504
Title: PerformanceTraceFormatter formatTraceSummary web-dev.json.gz
505505
Content:
506506
URL: https://web.dev/cls/
507-
Bounds: {min: 1020034823047, max: 1020036087961}
507+
Trace bounds: {min: 1020034823047, max: 1020036087961}
508508
CPU throttling: none
509509
Network throttling: none
510+
511+
# Available insight sets
512+
513+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
514+
515+
## insight set id: 86EB5E5C401E3E17ECE461B3FC627867
516+
517+
URL: https://web.dev/cls/
518+
Bounds: {min: 1020034834921, max: 1020036087961}
510519
Metrics (lab / observed):
511520
- LCP: 118 ms, event: (eventKey: r-1802, ts: 1020034953358), nodeId: 209
512521
- LCP breakdown:
@@ -537,9 +546,18 @@ Available insights:
537546
Title: PerformanceTraceFormatter formatTraceSummary yahoo-news.json.gz
538547
Content:
539548
URL: https://news.yahoo.com/
540-
Bounds: {min: 157423484442, max: 157427277166}
549+
Trace bounds: {min: 157423484442, max: 157427277166}
541550
CPU throttling: none
542551
Network throttling: none
552+
553+
# Available insight sets
554+
555+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
556+
557+
## insight set id: 513BDA1E9EC088611B53BFF7A859B5DD
558+
559+
URL: https://news.yahoo.com/
560+
Bounds: {min: 157423488682, max: 157427277166}
543561
Metrics (lab / observed):
544562
- LCP: 464 ms, event: (eventKey: r-33210, ts: 157423953162), nodeId: 8
545563
- LCP breakdown:
@@ -596,12 +614,81 @@ Available insights:
596614
example question: How can I reduce the amount of legacy JavaScript on my page?
597615
=== end content
598616

617+
Title: PerformanceTraceFormatter formatTraceSummary multiple-navigations-render-blocking.json.gz
618+
Content:
619+
URL: http://localhost:8080/render-blocking
620+
Trace bounds: {min: 171605647473, max: 171616667355}
621+
CPU throttling: 1x
622+
Network throttling: Fast 3G
623+
624+
# Available insight sets
625+
626+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
627+
628+
## insight set id: 8671F33ECE0C8DBAEFBC2F9A2D1D6107
629+
630+
URL: http://localhost:8080/render-blocking
631+
Bounds: {min: 171607579779, max: 171613750571}
632+
Metrics (lab / observed):
633+
- LCP: 1317 ms, event: (eventKey: r-6686, ts: 171608897210), nodeId: 10
634+
- LCP breakdown:
635+
- TTFB: 10 ms, bounds: {min: 171607579779, max: 171607589981.99997}
636+
- Render delay: 1,307 ms, bounds: {min: 171607589981.99997, max: 171608897210}
637+
- CLS: 0.00
638+
Metrics (field / real users): n/a – no data for this page in CrUX
639+
Available insights:
640+
- insight name: LCPBreakdown
641+
description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
642+
relevant trace bounds: {min: 171607579779, max: 171608897210}
643+
example question: Help me optimize my LCP score
644+
example question: Which LCP phase was most problematic?
645+
example question: What can I do to reduce the LCP time for this page load?
646+
- insight name: RenderBlocking
647+
description: Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://developer.chrome.com/docs/performance/insights/render-blocking) can move these network requests out of the critical path.
648+
relevant trace bounds: {min: 171608170438, max: 171608877165}
649+
example question: Show me the most impactful render blocking requests that I should focus on
650+
example question: How can I reduce the number of render blocking requests?
651+
652+
## insight set id: 1AE2016BBCC48AA090FDAE2CBBA01900
653+
654+
URL: http://localhost:8080/render-blocking
655+
Bounds: {min: 171613750571, max: 171616667355}
656+
Metrics (lab / observed):
657+
- LCP: 1310 ms, event: (eventKey: r-15639, ts: 171615060776), nodeId: 18
658+
- LCP breakdown:
659+
- TTFB: 3 ms, bounds: {min: 171613750571, max: 171613753188}
660+
- Render delay: 1,308 ms, bounds: {min: 171613753188, max: 171615060776}
661+
- CLS: 0.00
662+
Metrics (field / real users): n/a – no data for this page in CrUX
663+
Available insights:
664+
- insight name: LCPBreakdown
665+
description: Each [subpart has specific improvement strategies](https://developer.chrome.com/docs/performance/insights/lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.
666+
relevant trace bounds: {min: 171613750571, max: 171615060776}
667+
example question: Help me optimize my LCP score
668+
example question: Which LCP phase was most problematic?
669+
example question: What can I do to reduce the LCP time for this page load?
670+
- insight name: RenderBlocking
671+
description: Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://developer.chrome.com/docs/performance/insights/render-blocking) can move these network requests out of the critical path.
672+
relevant trace bounds: {min: 171614330544, max: 171615043224}
673+
example question: Show me the most impactful render blocking requests that I should focus on
674+
example question: How can I reduce the number of render blocking requests?
675+
=== end content
676+
599677
Title: PerformanceTraceFormatter formatTraceSummary deals with CrUX manager errors
600678
Content:
601679
URL: http://localhost/image-delivery-cases.html
602-
Bounds: {min: 59728641874, max: 59734400108}
680+
Trace bounds: {min: 59728641874, max: 59734400108}
603681
CPU throttling: 1x
604682
Network throttling: No throttling
683+
684+
# Available insight sets
685+
686+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
687+
688+
## insight set id: 9094E106056B51D078CE44F3356FE194
689+
690+
URL: http://localhost/image-delivery-cases.html
691+
Bounds: {min: 59728649746, max: 59734400108}
605692
Metrics (lab / observed):
606693
- LCP: 663 ms, event: (eventKey: r-14753, ts: 59729312744), nodeId: 12
607694
- LCP breakdown:
@@ -663,9 +750,18 @@ Available insights:
663750
Title: PerformanceTraceFormatter formatTraceSummary image-delivery.json.gz
664751
Content:
665752
URL: http://localhost/image-delivery-cases.html
666-
Bounds: {min: 59728641874, max: 59734400108}
753+
Trace bounds: {min: 59728641874, max: 59734400108}
667754
CPU throttling: 1x
668755
Network throttling: No throttling
756+
757+
# Available insight sets
758+
759+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
760+
761+
## insight set id: 9094E106056B51D078CE44F3356FE194
762+
763+
URL: http://localhost/image-delivery-cases.html
764+
Bounds: {min: 59728649746, max: 59734400108}
669765
Metrics (lab / observed):
670766
- LCP: 663 ms, event: (eventKey: r-14753, ts: 59729312744), nodeId: 12
671767
- LCP breakdown:
@@ -734,9 +830,18 @@ Available insights:
734830
Title: PerformanceTraceFormatter formatTraceSummary includes INP insight when there is no navigation
735831
Content:
736832
URL: https://b2607f8b04800100000289cb1c0a82ba72253000000000000000001.proxy.googlers.com/long-interaction/index.html?x=35
737-
Bounds: {min: 337943614456, max: 337947260898}
833+
Trace bounds: {min: 337943614456, max: 337947260898}
738834
CPU throttling: none
739835
Network throttling: none
836+
837+
# Available insight sets
838+
839+
The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.
840+
841+
## insight set id: NO_NAVIGATION
842+
843+
URL: https://b2607f8b04800100000289cb1c0a82ba72253000000000000000001.proxy.googlers.com/long-interaction/index.html?x=35
844+
Bounds: {min: 337943614456, max: 337947260898}
740845
Metrics (lab / observed):
741846
- INP: 139 ms, event: (eventKey: s-3347, ts: 337944870984)
742847
- CLS: 0.00

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ describeWithLocale('PerformanceTraceFormatter', function() {
4141
snapshotTester.assert(this, output);
4242
});
4343

44+
it('multiple-navigations-render-blocking.json.gz', async function() {
45+
const {formatter} = await createFormatter(this, 'multiple-navigations-render-blocking.json.gz');
46+
const output = formatter.formatTraceSummary();
47+
snapshotTester.assert(this, output);
48+
});
49+
4450
it('deals with CrUX manager errors', async function() {
4551
const {formatter} = await createFormatter(this, 'image-delivery.json.gz');
4652
sinon.stub(CrUXManager.CrUXManager, 'instance').callsFake(() => {

front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -116,62 +116,69 @@ export class PerformanceTraceFormatter {
116116

117117
formatTraceSummary(): string {
118118
const parsedTrace = this.#parsedTrace;
119-
const insightSet = this.#insightSet;
120119
const traceMetadata = this.#parsedTrace.metadata;
121120
const data = parsedTrace.data;
122121

123122
const parts = [];
124123

125-
const lcp = insightSet ? Trace.Insights.Common.getLCP(insightSet) : null;
126-
const cls = insightSet ? Trace.Insights.Common.getCLS(insightSet) : null;
127-
const inp = insightSet ? Trace.Insights.Common.getINP(insightSet) : null;
128-
129124
parts.push(`URL: ${data.Meta.mainFrameURL}`);
130-
parts.push(`Bounds: ${this.serializeBounds(data.Meta.traceBounds)}`);
125+
parts.push(`Trace bounds: ${this.serializeBounds(data.Meta.traceBounds)}`);
131126
parts.push('CPU throttling: ' + (traceMetadata.cpuThrottling ? `${traceMetadata.cpuThrottling}x` : 'none'));
132127
parts.push(`Network throttling: ${traceMetadata.networkThrottling ?? 'none'}`);
133-
if (lcp || cls || inp) {
134-
parts.push('Metrics (lab / observed):');
135-
if (lcp) {
136-
const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
137-
const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
138-
parts.push(
139-
` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
140-
const subparts = insightSet?.model.LCPBreakdown.subparts;
141-
if (subparts) {
142-
const serializeSubpart = (subpart: Trace.Insights.Models.LCPBreakdown.Subpart): string => {
143-
return `${micros(subpart.range)}, bounds: ${this.serializeBounds(subpart)}`;
144-
};
145-
parts.push(' - LCP breakdown:');
146-
parts.push(` - TTFB: ${serializeSubpart(subparts.ttfb)}`);
147-
if (subparts.loadDelay !== undefined) {
148-
parts.push(` - Load delay: ${serializeSubpart(subparts.loadDelay)}`);
149-
}
150-
if (subparts.loadDuration !== undefined) {
151-
parts.push(` - Load duration: ${serializeSubpart(subparts.loadDuration)}`);
128+
129+
parts.push('\n# Available insight sets\n');
130+
parts.push(
131+
'The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.');
132+
133+
for (const insightSet of parsedTrace.insights?.values() ?? []) {
134+
const lcp = insightSet ? Trace.Insights.Common.getLCP(insightSet) : null;
135+
const cls = insightSet ? Trace.Insights.Common.getCLS(insightSet) : null;
136+
const inp = insightSet ? Trace.Insights.Common.getINP(insightSet) : null;
137+
138+
parts.push(`\n## insight set id: ${insightSet.id}\n`);
139+
parts.push(`URL: ${insightSet.url}`);
140+
parts.push(`Bounds: ${this.serializeBounds(insightSet.bounds)}`);
141+
if (lcp || cls || inp) {
142+
parts.push('Metrics (lab / observed):');
143+
if (lcp) {
144+
const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
145+
const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
146+
parts.push(
147+
` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
148+
const subparts = insightSet?.model.LCPBreakdown.subparts;
149+
if (subparts) {
150+
const serializeSubpart = (subpart: Trace.Insights.Models.LCPBreakdown.Subpart): string => {
151+
return `${micros(subpart.range)}, bounds: ${this.serializeBounds(subpart)}`;
152+
};
153+
parts.push(' - LCP breakdown:');
154+
parts.push(` - TTFB: ${serializeSubpart(subparts.ttfb)}`);
155+
if (subparts.loadDelay !== undefined) {
156+
parts.push(` - Load delay: ${serializeSubpart(subparts.loadDelay)}`);
157+
}
158+
if (subparts.loadDuration !== undefined) {
159+
parts.push(` - Load duration: ${serializeSubpart(subparts.loadDuration)}`);
160+
}
161+
parts.push(` - Render delay: ${serializeSubpart(subparts.renderDelay)}`);
152162
}
153-
parts.push(` - Render delay: ${serializeSubpart(subparts.renderDelay)}`);
154163
}
164+
if (inp) {
165+
parts.push(` - INP: ${Math.round(inp.value / 1000)} ms, event: ${this.serializeEvent(inp.event)}`);
166+
}
167+
if (cls) {
168+
const eventText = cls.worstClusterEvent ? `, event: ${this.serializeEvent(cls.worstClusterEvent)}` : '';
169+
parts.push(` - CLS: ${cls.value.toFixed(2)}${eventText}`);
170+
}
171+
} else {
172+
parts.push('Metrics (lab / observed): n/a');
155173
}
156-
if (inp) {
157-
parts.push(` - INP: ${Math.round(inp.value / 1000)} ms, event: ${this.serializeEvent(inp.event)}`);
158-
}
159-
if (cls) {
160-
const eventText = cls.worstClusterEvent ? `, event: ${this.serializeEvent(cls.worstClusterEvent)}` : '';
161-
parts.push(` - CLS: ${cls.value.toFixed(2)}${eventText}`);
162-
}
163-
} else {
164-
parts.push('Metrics (lab / observed): n/a');
165-
}
166174

167-
const cruxParts = insightSet && this.#getCruxTraceSummary(insightSet);
168-
if (cruxParts?.length) {
169-
parts.push(...cruxParts);
170-
} else {
171-
parts.push('Metrics (field / real users): n/a – no data for this page in CrUX');
172-
}
175+
const cruxParts = insightSet && this.#getCruxTraceSummary(insightSet);
176+
if (cruxParts?.length) {
177+
parts.push(...cruxParts);
178+
} else {
179+
parts.push('Metrics (field / real users): n/a – no data for this page in CrUX');
180+
}
173181

174-
if (insightSet) {
175182
parts.push('Available insights:');
176183
for (const [insightName, model] of Object.entries(insightSet.model)) {
177184
if (model.state === 'pass') {
@@ -202,8 +209,6 @@ export class PerformanceTraceFormatter {
202209
const insightPartsText = insightParts.join('\n ');
203210
parts.push(` - ${insightPartsText}`);
204211
}
205-
} else {
206-
parts.push('Available insights: none');
207212
}
208213

209214
return parts.join('\n');

0 commit comments

Comments
 (0)