Skip to content

Commit d2604d0

Browse files
committed
(do not merge): exposing additional performance functions
This PR is so we have a branch that has the bigger set of performance functions available to it that are equivalent to the ones the DevTools Performance Agent has. If we decide we want to expose these functions, we will do so via reusing the implementation from upstream, not via the copy/paste of this PR, but this is the quickest way to get them available to test.
1 parent 2714158 commit d2604d0

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

src/tools/performance.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import type {Page} from 'puppeteer-core';
88
import z from 'zod';
99

10+
import {PerformanceTraceFormatter} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js';
11+
import {AICallTree} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js';
12+
import {AgentFocus} from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js';
13+
import * as TraceEngine from '../../node_modules/chrome-devtools-frontend/front_end/models/trace/trace.js';
1014
import {logger} from '../logger.js';
1115
import type {InsightName} from '../trace-processing/parse.js';
1216
import {
@@ -189,3 +193,157 @@ async function stopTracingAndAppendOutput(
189193
context.setIsRunningPerformanceTrace(false);
190194
}
191195
}
196+
197+
export const getEventByKey = defineTool({
198+
name: 'performance_get_event_by_key',
199+
description:
200+
'Returns detailed information about a specific event. Use the detail returned to validate performance issues, but do not tell the user about irrelevant raw data from a trace event.',
201+
annotations: {
202+
category: ToolCategories.PERFORMANCE,
203+
readOnlyHint: true,
204+
},
205+
schema: {
206+
eventKey: z.string().describe('The key for the event.'),
207+
},
208+
handler: async (request, response, context) => {
209+
const trace = context.recordedTraces().at(-1);
210+
if (!trace) {
211+
response.appendResponseLine('Error: no trace recorded');
212+
return;
213+
}
214+
const focus = AgentFocus.fromParsedTrace(trace.parsedTrace);
215+
const event = focus.lookupEvent(
216+
request.params.eventKey as TraceEngine.Types.File.SerializableKey,
217+
);
218+
if (!event) {
219+
response.appendResponseLine('Error: no event with key found');
220+
return;
221+
}
222+
response.appendResponseLine(`Event:\n${JSON.stringify(event, null, 2)}`);
223+
},
224+
});
225+
226+
export const getMainThreadTrackSummary = defineTool({
227+
name: 'performance_get_main_thread_track_summary',
228+
description:
229+
'Returns a summary of the main thread for the given bounds. The result includes a top-down summary, bottom-up summary, third-parties summary, and a list of related insights for the events within the given bounds.',
230+
annotations: {
231+
category: ToolCategories.PERFORMANCE,
232+
readOnlyHint: true,
233+
},
234+
schema: {
235+
min: z.number().describe('The minimum time of the bounds, in microseconds'),
236+
max: z.number().describe('The maximum time of the bounds, in microseconds'),
237+
},
238+
handler: async (request, response, context) => {
239+
const trace = context.recordedTraces().at(-1);
240+
if (!trace) {
241+
response.appendResponseLine('Error: no trace recorded');
242+
return;
243+
}
244+
const bounds = createBounds(
245+
trace.parsedTrace,
246+
request.params.min as TraceEngine.Types.Timing.Micro,
247+
request.params.max as TraceEngine.Types.Timing.Micro,
248+
);
249+
if (!bounds) {
250+
response.appendResponseLine('Erorr: invalid trace bounds');
251+
return;
252+
}
253+
254+
const focus = AgentFocus.fromParsedTrace(trace.parsedTrace);
255+
const formatter = new PerformanceTraceFormatter(focus);
256+
response.appendResponseLine(formatter.formatMainThreadTrackSummary(bounds));
257+
},
258+
});
259+
260+
export const getNetworkTrackSummary = defineTool({
261+
name: 'performance_get_network_track_summary',
262+
description: 'Returns a summary of the network for the given bounds.',
263+
annotations: {
264+
category: ToolCategories.PERFORMANCE,
265+
readOnlyHint: true,
266+
},
267+
schema: {
268+
min: z.number().describe('The minimum time of the bounds, in microseconds'),
269+
max: z.number().describe('The maximum time of the bounds, in microseconds'),
270+
},
271+
handler: async (request, response, context) => {
272+
const trace = context.recordedTraces().at(-1);
273+
if (!trace) {
274+
response.appendResponseLine('Error: no trace recorded');
275+
return;
276+
}
277+
const bounds = createBounds(
278+
trace.parsedTrace,
279+
request.params.min as TraceEngine.Types.Timing.Micro,
280+
request.params.max as TraceEngine.Types.Timing.Micro,
281+
);
282+
if (!bounds) {
283+
response.appendResponseLine('Erorr: invalid trace bounds');
284+
return;
285+
}
286+
287+
const focus = AgentFocus.fromParsedTrace(trace.parsedTrace);
288+
const formatter = new PerformanceTraceFormatter(focus);
289+
response.appendResponseLine(formatter.formatNetworkTrackSummary(bounds));
290+
},
291+
});
292+
293+
export const getDetailedCallTree = defineTool({
294+
name: 'performance_get_detailed_call_tree',
295+
description: 'Returns a detailed call tree for the given main thread event.',
296+
annotations: {
297+
category: ToolCategories.PERFORMANCE,
298+
readOnlyHint: true,
299+
},
300+
schema: {
301+
eventKey: z.string().describe('The key for the event.'),
302+
},
303+
handler: async (request, response, context) => {
304+
const trace = context.recordedTraces().at(-1);
305+
if (!trace) {
306+
response.appendResponseLine('Error: no trace recorded');
307+
return;
308+
}
309+
const focus = AgentFocus.fromParsedTrace(trace.parsedTrace);
310+
const event = focus.lookupEvent(
311+
request.params.eventKey as TraceEngine.Types.File.SerializableKey,
312+
);
313+
if (!event) {
314+
response.appendResponseLine('Error: no event with key found');
315+
return;
316+
}
317+
const formatter = new PerformanceTraceFormatter(focus);
318+
const tree = AICallTree.fromEvent(event, trace.parsedTrace);
319+
const callTree = tree
320+
? formatter.formatCallTree(tree)
321+
: 'No call tree found';
322+
response.appendResponseLine(callTree);
323+
},
324+
});
325+
326+
const createBounds = (
327+
trace: TraceEngine.TraceModel.ParsedTrace,
328+
min: TraceEngine.Types.Timing.Micro,
329+
max: TraceEngine.Types.Timing.Micro,
330+
): TraceEngine.Types.Timing.TraceWindowMicro | null => {
331+
if (min > max) {
332+
return null;
333+
}
334+
335+
const clampedMin = Math.max(min ?? 0, trace.data.Meta.traceBounds.min);
336+
const clampedMax = Math.min(
337+
max ?? Number.POSITIVE_INFINITY,
338+
trace.data.Meta.traceBounds.max,
339+
);
340+
if (clampedMin > clampedMax) {
341+
return null;
342+
}
343+
344+
return TraceEngine.Helpers.Timing.traceWindowFromMicroSeconds(
345+
clampedMin as TraceEngine.Types.Timing.Micro,
346+
clampedMax as TraceEngine.Types.Timing.Micro,
347+
);
348+
};
349+

0 commit comments

Comments
 (0)