gcp-profiler-compare-trends
Analyze and compare performance trends in Google Cloud Profiler data to identify patterns and optimize application performance.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/services/profiler/tools.ts:366-462 (handler)Main handler function that authenticates to GCP Profiler API, fetches and filters profiles based on input parameters, generates trend analysis using helper function, and returns markdown report.async ({ target, profileType, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access profiler data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); const actualPageSize = pageSize || 200; // Build query parameters const params = new URLSearchParams({ pageSize: actualPageSize.toString(), }); // Make REST API call to list profiles const apiUrl = `https://cloudprofiler.googleapis.com/v2/projects/${projectId}/profiles?${params}`; const response = await fetch(apiUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch profiles for trend analysis: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data: ListProfilesResponse = await response.json(); let profiles = data.profiles || []; // Apply filtering if (profileType) { profiles = profiles.filter((p) => p.profileType === profileType); } if (target) { profiles = profiles.filter((p) => p.deployment?.target?.toLowerCase().includes(target.toLowerCase()), ); } if (!profiles || profiles.length === 0) { return { content: [ { type: "text", text: `# Profile Trend Analysis\n\nProject: ${projectId}\n\nNo profiles found for trend analysis.`, }, ], }; } // Generate trend analysis let content = `# Profile Trend Analysis\n\nProject: ${projectId}\n`; if (profileType) content += `Profile Type: ${getProfileTypeDescription(profileType)}\n`; if (target) content += `Target: ${target}\n`; content += `Analysed: ${profiles.length} profiles\n\n`; // Analyse trends over time const trendAnalysis = analyseProfileTrends(profiles); content += trendAnalysis; return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse profile trends: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } },
- src/services/profiler/tools.ts:336-463 (registration)Registers the 'gcp-profiler-compare-trends' tool with the MCP server, including title, description, Zod input schema, and handler reference."gcp-profiler-compare-trends", { title: "Compare Profile Trends", description: "Compare profiles over time to identify performance trends, regressions, and improvements", inputSchema: { target: z .string() .optional() .describe("Focus comparison on specific deployment target"), profileType: z .enum([ ProfileType.CPU, ProfileType.WALL, ProfileType.HEAP, ProfileType.THREADS, ProfileType.CONTENTION, ProfileType.PEAK_HEAP, ProfileType.HEAP_ALLOC, ]) .optional() .describe("Focus comparison on specific profile type"), pageSize: z .number() .min(1) .max(1000) .default(200) .describe("Number of profiles to analyse for trends"), }, }, async ({ target, profileType, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access profiler data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); const actualPageSize = pageSize || 200; // Build query parameters const params = new URLSearchParams({ pageSize: actualPageSize.toString(), }); // Make REST API call to list profiles const apiUrl = `https://cloudprofiler.googleapis.com/v2/projects/${projectId}/profiles?${params}`; const response = await fetch(apiUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch profiles for trend analysis: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data: ListProfilesResponse = await response.json(); let profiles = data.profiles || []; // Apply filtering if (profileType) { profiles = profiles.filter((p) => p.profileType === profileType); } if (target) { profiles = profiles.filter((p) => p.deployment?.target?.toLowerCase().includes(target.toLowerCase()), ); } if (!profiles || profiles.length === 0) { return { content: [ { type: "text", text: `# Profile Trend Analysis\n\nProject: ${projectId}\n\nNo profiles found for trend analysis.`, }, ], }; } // Generate trend analysis let content = `# Profile Trend Analysis\n\nProject: ${projectId}\n`; if (profileType) content += `Profile Type: ${getProfileTypeDescription(profileType)}\n`; if (target) content += `Target: ${target}\n`; content += `Analysed: ${profiles.length} profiles\n\n`; // Analyse trends over time const trendAnalysis = analyseProfileTrends(profiles); content += trendAnalysis; return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse profile trends: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, );
- Zod schema defining the input parameters for the tool: target (optional string), profileType (optional enum), pageSize (number default 200).inputSchema: { target: z .string() .optional() .describe("Focus comparison on specific deployment target"), profileType: z .enum([ ProfileType.CPU, ProfileType.WALL, ProfileType.HEAP, ProfileType.THREADS, ProfileType.CONTENTION, ProfileType.PEAK_HEAP, ProfileType.HEAP_ALLOC, ]) .optional() .describe("Focus comparison on specific profile type"), pageSize: z .number() .min(1) .max(1000) .default(200) .describe("Number of profiles to analyse for trends"), },
- Supporting helper function called by the handler to analyze profile trends over time, calculating frequency, type distribution per day, and providing recommendations based on data coverage.function analyseProfileTrends(profiles: Profile[]): string { const profilesByTime = profiles .filter((p) => p.startTime) .sort( (a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime(), ); if (profilesByTime.length < 2) { return "Insufficient time-series data for trend analysis. Need at least 2 time-stamped profiles.\n"; } let analysis = "## Trend Analysis\n\n"; // Analyse profile frequency over time analysis += "### Profile Collection Frequency\n\n"; const earliest = new Date(profilesByTime[0].startTime); const latest = new Date(profilesByTime[profilesByTime.length - 1].startTime); const timeSpan = latest.getTime() - earliest.getTime(); const timeSpanDays = timeSpan / (1000 * 60 * 60 * 24); analysis += `- **Time Span:** ${Math.round(timeSpanDays)} days (from ${earliest.toLocaleDateString()} to ${latest.toLocaleDateString()})\n`; analysis += `- **Collection Frequency:** ${Math.round(profiles.length / timeSpanDays)} profiles per day\n\n`; // Analyse profile type trends analysis += "### Profile Type Trends\n\n"; const typesByTime = profilesByTime.reduce( (acc, profile) => { const day = new Date(profile.startTime).toDateString(); if (!acc[day]) acc[day] = {}; acc[day][profile.profileType] = (acc[day][profile.profileType] || 0) + 1; return acc; }, {} as Record<string, Record<string, number>>, ); const days = Object.keys(typesByTime).sort(); if (days.length > 1) { analysis += `Collected profiles across ${days.length} different days:\n\n`; days.slice(-5).forEach((day) => { // Show last 5 days const typeCounts = typesByTime[day]; const totalForDay = Object.values(typeCounts).reduce( (sum, count) => sum + count, 0, ); analysis += `**${day}:** ${totalForDay} profiles (`; analysis += Object.entries(typeCounts) .map(([type, count]) => `${type}: ${count}`) .join(", "); analysis += `)\n`; }); analysis += `\n`; } // Recommendations based on trends analysis += "### Trend-Based Recommendations\n\n"; if (timeSpanDays < 1) { analysis += `- **Short timeframe:** Consider collecting profiles over a longer period for better trend analysis\n`; } else if (profiles.length / timeSpanDays < 1) { analysis += `- **Low frequency:** Consider increasing profile collection frequency for better insights\n`; } else { analysis += `- **Good coverage:** Profile collection frequency appears adequate for trend analysis\n`; } analysis += `- **Pattern monitoring:** Set up alerts for unusual changes in profile patterns\n`; analysis += `- **Performance baseline:** Use this trend data to establish performance baselines\n`; return analysis; }