mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add logic to prepare perf data for LLM analysis
This commit is contained in:
+1583
File diff suppressed because it is too large
Load Diff
@@ -459,18 +459,25 @@ server.tool(
|
||||
server.tool(
|
||||
'get-react-performance-data',
|
||||
`
|
||||
This tool returns the JSON format of the performance data recorded by the start-react-performance-recording tool.
|
||||
It retrieves the data that was captured from console.timeStamp calls in the browser, which includes information from
|
||||
This tool retrieves the performance data recorded by the start-react-performance-recording tool,
|
||||
converts it to CSV format, and saves it to a file in the artifacts directory.
|
||||
It processes the data that was captured from console.timeStamp calls in the browser, which includes information from
|
||||
the React Performance panel in Chrome DevTools (Components track and Scheduler track).
|
||||
|
||||
<requirements>
|
||||
- The url should be a full url with the protocol (http:// or https://) and the domain name (e.g. localhost:3000).
|
||||
- The user should be running a Chrome browser in debug mode on port 9222. If you receive an error message, advise the user to run
|
||||
the following command in the terminal:
|
||||
MacOS: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome"
|
||||
Windows: "chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\temp\chrome"
|
||||
- You must have previously run the start-react-performance-recording tool to capture performance data.
|
||||
- The data will be available after the user has interacted with their app while recording was active.
|
||||
</requirements>
|
||||
|
||||
<usage>
|
||||
- Use this tool after running start-react-performance-recording and having the user interact with their app.
|
||||
- The returned data will be in JSON format, containing detailed performance metrics that can be analyzed.
|
||||
- The tool will save the performance data as a CSV file in the artifacts directory.
|
||||
- The response will include the path to the saved file.
|
||||
</usage>
|
||||
`,
|
||||
{
|
||||
@@ -478,13 +485,13 @@ server.tool(
|
||||
},
|
||||
async ({url}) => {
|
||||
try {
|
||||
const perfData = await getPerfData(url);
|
||||
const result = await getPerfData(url);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: JSON.stringify(perfData, null, 2),
|
||||
text: result,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import {hookIntoPage} from '../utils/puppeteerUtils';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Connects to a browser and patches the console.timeStamp method to capture data
|
||||
@@ -8,25 +10,52 @@ import {hookIntoPage} from '../utils/puppeteerUtils';
|
||||
* @param url The URL of the page to connect to
|
||||
* @returns A promise that resolves to the captured console.timeStamp data
|
||||
*/
|
||||
export async function beginPerfRecording(url: string): Promise<string[]> {
|
||||
export async function beginPerfRecording(url: string): Promise<string> {
|
||||
try {
|
||||
const targetPage = await hookIntoPage(url);
|
||||
|
||||
await targetPage.evaluateOnNewDocument(() => {
|
||||
(window as any).__CAPTURED_TIMESTAMP_DATA__ = [];
|
||||
(window as any).__COMPONENT_TRACK_TIMESTAMP_DATA__ = [];
|
||||
(window as any).__SCHEDULER_TRACK_TIMESTAMP_DATA__ = [];
|
||||
(window as any).__MCP_RECORDING_ACTIVE__ = true;
|
||||
|
||||
const originalTimestamp = console.timeStamp;
|
||||
|
||||
// Monkey-patch console.timeStamp to capture data
|
||||
console.timeStamp = function (...args) {
|
||||
console.timeStamp = function (...args: any) {
|
||||
if ((window as any).__MCP_RECORDING_ACTIVE__) {
|
||||
console.log('[MCP] console.timestamp called with', ...args);
|
||||
(window as any).__CAPTURED_TIMESTAMP_DATA__.push(args);
|
||||
|
||||
if ((args[4] as string)?.includes('Scheduler')) {
|
||||
const timeStampData = {
|
||||
name: args[0],
|
||||
startTime: args[1],
|
||||
endTime: args[2],
|
||||
type: args[3],
|
||||
track: 'Scheduler',
|
||||
color: args[5],
|
||||
};
|
||||
(window as any).__SCHEDULER_TRACK_TIMESTAMP_DATA__.push(
|
||||
timeStampData,
|
||||
);
|
||||
} else if ((args[3] as string)?.includes('Components')) {
|
||||
const timeStampData = {
|
||||
name: args[0],
|
||||
startTime: args[1],
|
||||
endTime: args[2],
|
||||
track: 'Components',
|
||||
color: args[4],
|
||||
};
|
||||
(window as any).__COMPONENT_TRACK_TIMESTAMP_DATA__.push(
|
||||
timeStampData,
|
||||
);
|
||||
} else {
|
||||
console.log('[MCP] Unknown track format:', args);
|
||||
}
|
||||
}
|
||||
|
||||
if (originalTimestamp) {
|
||||
return originalTimestamp.apply(console, args);
|
||||
return originalTimestamp.apply(console, args as any);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,10 +68,6 @@ export async function beginPerfRecording(url: string): Promise<string[]> {
|
||||
if (mcpStyle) mcpStyle.remove();
|
||||
if (mcpIndicator) mcpIndicator.remove();
|
||||
|
||||
console.log(
|
||||
'[MCP] Recording stopped, captured data:',
|
||||
(window as any).__CAPTURED_TIMESTAMP_DATA__,
|
||||
);
|
||||
return (window as any).__CAPTURED_TIMESTAMP_DATA__;
|
||||
};
|
||||
|
||||
@@ -107,20 +132,137 @@ export async function beginPerfRecording(url: string): Promise<string[]> {
|
||||
|
||||
await targetPage.reload({waitUntil: 'domcontentloaded'});
|
||||
|
||||
const capturedTimeStampData = await targetPage.evaluate(() => {
|
||||
return (window as any).__CAPTURED_TIMESTAMP_DATA__ || [];
|
||||
});
|
||||
|
||||
return capturedTimeStampData;
|
||||
return 'Recording Successfully Started';
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to capture console.timeStamp data: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPerfData(url: string): Promise<string[]> {
|
||||
const page = await hookIntoPage(url);
|
||||
export async function getPerfData(url: string): Promise<string> {
|
||||
try {
|
||||
const page = await hookIntoPage(url);
|
||||
|
||||
return await page.evaluate(() => {
|
||||
return (window as any).__CAPTURED_TIMESTAMP_DATA__;
|
||||
});
|
||||
// Get both timestamp data tracks from the browser
|
||||
const [componentTrackData, schedulerTrackData] = await page.evaluate(() => {
|
||||
return [
|
||||
(window as any).__COMPONENT_TRACK_TIMESTAMP_DATA__ || [],
|
||||
(window as any).__SCHEDULER_TRACK_TIMESTAMP_DATA__ || [],
|
||||
];
|
||||
});
|
||||
|
||||
if (
|
||||
(!Array.isArray(componentTrackData) || componentTrackData.length === 0) &&
|
||||
(!Array.isArray(schedulerTrackData) || schedulerTrackData.length === 0)
|
||||
) {
|
||||
return 'No performance data was captured. Make sure to run start-react-performance-recording first.';
|
||||
}
|
||||
|
||||
// Create a timestamp for the filenames
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const artifactsDir = path.join(__dirname, '../src/artifacts');
|
||||
|
||||
// Ensure the artifacts directory exists
|
||||
try {
|
||||
await fs.mkdir(artifactsDir, {recursive: true});
|
||||
} catch (err) {
|
||||
console.error(`Error creating directory: ${err}`);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
// Process Component Track data
|
||||
if (Array.isArray(componentTrackData) && componentTrackData.length > 0) {
|
||||
const componentFilename = `react-component-track-${timestamp}.csv`;
|
||||
const componentFilePath = path.join(artifactsDir, componentFilename);
|
||||
|
||||
// Define headers for component track
|
||||
const componentHeaders = [
|
||||
'name',
|
||||
'startTime',
|
||||
'endTime',
|
||||
'track',
|
||||
'color',
|
||||
].join(',');
|
||||
|
||||
// Convert data to CSV rows
|
||||
const componentRows = componentTrackData.map(item => {
|
||||
return componentHeaders
|
||||
.split(',')
|
||||
.map(header => {
|
||||
const value = item[header];
|
||||
// Handle undefined values
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
// Handle strings with commas by wrapping in quotes
|
||||
if (typeof value === 'string' && value.includes(',')) {
|
||||
return `"${value}"`;
|
||||
}
|
||||
return value;
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
// Combine headers and rows
|
||||
const componentCsvContent = [componentHeaders, ...componentRows].join(
|
||||
'\n',
|
||||
);
|
||||
|
||||
// Write to file
|
||||
await fs.writeFile(componentFilePath, componentCsvContent, 'utf8');
|
||||
|
||||
results.push(`Component track data saved to ${componentFilename}`);
|
||||
}
|
||||
|
||||
// Process Scheduler Track data
|
||||
if (Array.isArray(schedulerTrackData) && schedulerTrackData.length > 0) {
|
||||
const schedulerFilename = `react-scheduler-track-${timestamp}.csv`;
|
||||
const schedulerFilePath = path.join(artifactsDir, schedulerFilename);
|
||||
|
||||
// Define headers for scheduler track
|
||||
const schedulerHeaders = [
|
||||
'name',
|
||||
'startTime',
|
||||
'endTime',
|
||||
'type',
|
||||
'track',
|
||||
'color',
|
||||
].join(',');
|
||||
|
||||
// Convert data to CSV rows
|
||||
const schedulerRows = schedulerTrackData.map(item => {
|
||||
return schedulerHeaders
|
||||
.split(',')
|
||||
.map(header => {
|
||||
const value = item[header];
|
||||
// Handle undefined values
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
// Handle strings with commas by wrapping in quotes
|
||||
if (typeof value === 'string' && value.includes(',')) {
|
||||
return `"${value}"`;
|
||||
}
|
||||
return value;
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
// Combine headers and rows
|
||||
const schedulerCsvContent = [schedulerHeaders, ...schedulerRows].join(
|
||||
'\n',
|
||||
);
|
||||
|
||||
// Write to file
|
||||
await fs.writeFile(schedulerFilePath, schedulerCsvContent, 'utf8');
|
||||
|
||||
results.push(`Scheduler track data saved to ${schedulerFilename}`);
|
||||
}
|
||||
|
||||
return results.length > 0
|
||||
? `Performance data saved in the artifacts directory:\n${results.join('\n')}`
|
||||
: 'No performance data was available to save.';
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to process performance data: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user