Skip to main content
Glama
performance-monitor.ts9.25 kB
/** * Performance monitoring utilities for pagination operations * Tracks timing, memory usage, and API call efficiency */ export interface PaginationMetrics { startTime: number; endTime?: number; totalDuration?: number; apiCallCount: number; totalItemsRetrieved: number; averageItemsPerCall: number; memoryUsage?: { heapUsed: number; heapTotal: number; external: number; }; rateLimitHits: number; errorCount: number; largestBatchSize: number; smallestBatchSize: number; } export interface PerformanceThresholds { maxDurationMs: number; maxMemoryMB: number; maxApiCalls: number; minItemsPerCall: number; } export class PaginationPerformanceMonitor { private metrics: PaginationMetrics; private thresholds: PerformanceThresholds; private batchSizes: number[] = []; constructor(thresholds?: Partial<PerformanceThresholds>) { this.metrics = { startTime: Date.now(), apiCallCount: 0, totalItemsRetrieved: 0, averageItemsPerCall: 0, rateLimitHits: 0, errorCount: 0, largestBatchSize: 0, smallestBatchSize: Infinity }; this.thresholds = { maxDurationMs: 60000, // 1 minute maxMemoryMB: 100, // 100MB maxApiCalls: 50, minItemsPerCall: 10, ...thresholds }; } /** * Record an API call with its results */ recordApiCall(itemsRetrieved: number, isRateLimited: boolean = false, hasError: boolean = false): void { this.metrics.apiCallCount++; this.metrics.totalItemsRetrieved += itemsRetrieved; this.batchSizes.push(itemsRetrieved); if (itemsRetrieved > this.metrics.largestBatchSize) { this.metrics.largestBatchSize = itemsRetrieved; } if (itemsRetrieved < this.metrics.smallestBatchSize && itemsRetrieved > 0) { this.metrics.smallestBatchSize = itemsRetrieved; } if (isRateLimited) { this.metrics.rateLimitHits++; } if (hasError) { this.metrics.errorCount++; } // Update average this.metrics.averageItemsPerCall = this.metrics.totalItemsRetrieved / this.metrics.apiCallCount; // Log progress for long operations if (this.metrics.apiCallCount % 5 === 0) { this.logProgress(); } } /** * Record memory usage at current point */ recordMemoryUsage(): void { const memUsage = process.memoryUsage(); this.metrics.memoryUsage = { heapUsed: memUsage.heapUsed, heapTotal: memUsage.heapTotal, external: memUsage.external }; } /** * Finalize metrics and return performance summary */ finalize(): PaginationMetrics & { performanceWarnings: string[] } { this.metrics.endTime = Date.now(); this.metrics.totalDuration = this.metrics.endTime - this.metrics.startTime; this.recordMemoryUsage(); const warnings = this.checkPerformanceThresholds(); console.error(`[Performance Monitor] Pagination completed:`); console.error(` Duration: ${this.metrics.totalDuration}ms`); console.error(` API Calls: ${this.metrics.apiCallCount}`); console.error(` Items Retrieved: ${this.metrics.totalItemsRetrieved}`); console.error(` Avg Items/Call: ${this.metrics.averageItemsPerCall.toFixed(1)}`); console.error(` Memory Used: ${this.formatMemoryUsage()}`); if (warnings.length > 0) { console.error(` ⚠️ Performance Warnings: ${warnings.length}`); warnings.forEach(warning => console.error(` - ${warning}`)); } else { console.error(` ✅ Performance: All thresholds met`); } return { ...this.metrics, performanceWarnings: warnings }; } /** * Check if performance metrics exceed thresholds */ private checkPerformanceThresholds(): string[] { const warnings: string[] = []; if (this.metrics.totalDuration && this.metrics.totalDuration > this.thresholds.maxDurationMs) { warnings.push(`Duration exceeded threshold: ${this.metrics.totalDuration}ms > ${this.thresholds.maxDurationMs}ms`); } if (this.metrics.apiCallCount > this.thresholds.maxApiCalls) { warnings.push(`API calls exceeded threshold: ${this.metrics.apiCallCount} > ${this.thresholds.maxApiCalls}`); } if (this.metrics.averageItemsPerCall < this.thresholds.minItemsPerCall) { warnings.push(`Low efficiency: ${this.metrics.averageItemsPerCall.toFixed(1)} items/call < ${this.thresholds.minItemsPerCall}`); } if (this.metrics.memoryUsage) { const memoryMB = this.metrics.memoryUsage.heapUsed / 1024 / 1024; if (memoryMB > this.thresholds.maxMemoryMB) { warnings.push(`Memory usage exceeded threshold: ${memoryMB.toFixed(1)}MB > ${this.thresholds.maxMemoryMB}MB`); } } if (this.metrics.rateLimitHits > 0) { warnings.push(`Rate limit hits detected: ${this.metrics.rateLimitHits}`); } if (this.metrics.errorCount > 0) { warnings.push(`Errors encountered: ${this.metrics.errorCount}`); } return warnings; } /** * Log current progress */ private logProgress(): void { const elapsed = Date.now() - this.metrics.startTime; console.error(`[Performance Monitor] Progress: ${this.metrics.apiCallCount} calls, ${this.metrics.totalItemsRetrieved} items, ${elapsed}ms elapsed`); } /** * Format memory usage for display */ private formatMemoryUsage(): string { if (!this.metrics.memoryUsage) return 'N/A'; const heapMB = (this.metrics.memoryUsage.heapUsed / 1024 / 1024).toFixed(1); const totalMB = (this.metrics.memoryUsage.heapTotal / 1024 / 1024).toFixed(1); return `${heapMB}MB / ${totalMB}MB heap`; } /** * Get current metrics without finalizing */ getCurrentMetrics(): PaginationMetrics { return { ...this.metrics }; } /** * Check if operation should be aborted due to performance issues */ shouldAbort(): { abort: boolean; reason?: string } { const elapsed = Date.now() - this.metrics.startTime; if (elapsed > this.thresholds.maxDurationMs) { return { abort: true, reason: `Duration exceeded ${this.thresholds.maxDurationMs}ms` }; } if (this.metrics.apiCallCount > this.thresholds.maxApiCalls) { return { abort: true, reason: `API calls exceeded ${this.thresholds.maxApiCalls}` }; } if (this.metrics.memoryUsage) { const memoryMB = this.metrics.memoryUsage.heapUsed / 1024 / 1024; if (memoryMB > this.thresholds.maxMemoryMB) { return { abort: true, reason: `Memory usage exceeded ${this.thresholds.maxMemoryMB}MB` }; } } return { abort: false }; } /** * Get performance recommendations based on current metrics */ getRecommendations(): string[] { const recommendations: string[] = []; if (this.metrics.averageItemsPerCall < 50 && this.metrics.apiCallCount > 10) { recommendations.push('Consider increasing batch size to improve efficiency'); } if (this.metrics.rateLimitHits > 0) { recommendations.push('Add delays between API calls to avoid rate limiting'); } if (this.batchSizes.length > 0) { const variance = this.calculateBatchSizeVariance(); if (variance > 0.5) { recommendations.push('Batch sizes vary significantly - consider consistent sizing'); } } const elapsed = Date.now() - this.metrics.startTime; if (elapsed > 30000 && this.metrics.totalItemsRetrieved < 1000) { recommendations.push('Operation taking long time for small dataset - check API performance'); } return recommendations; } /** * Calculate variance in batch sizes */ private calculateBatchSizeVariance(): number { if (this.batchSizes.length < 2) return 0; const mean = this.batchSizes.reduce((sum, size) => sum + size, 0) / this.batchSizes.length; const variance = this.batchSizes.reduce((sum, size) => sum + Math.pow(size - mean, 2), 0) / this.batchSizes.length; return Math.sqrt(variance) / mean; // Coefficient of variation } } /** * Create a performance monitor with default thresholds for different operation types */ export function createPerformanceMonitor(operationType: 'accounts' | 'campaigns' | 'emails' | 'leads'): PaginationPerformanceMonitor { const thresholds: Partial<PerformanceThresholds> = {}; switch (operationType) { case 'accounts': // Accounts are typically smaller datasets thresholds.maxDurationMs = 30000; // 30 seconds thresholds.maxApiCalls = 20; thresholds.minItemsPerCall = 20; break; case 'campaigns': // Campaigns can be moderate size thresholds.maxDurationMs = 45000; // 45 seconds thresholds.maxApiCalls = 30; thresholds.minItemsPerCall = 15; break; case 'emails': // Emails can be very large datasets thresholds.maxDurationMs = 120000; // 2 minutes thresholds.maxApiCalls = 100; thresholds.minItemsPerCall = 50; thresholds.maxMemoryMB = 200; break; case 'leads': // Leads can be very large datasets thresholds.maxDurationMs = 180000; // 3 minutes thresholds.maxApiCalls = 150; thresholds.minItemsPerCall = 100; thresholds.maxMemoryMB = 300; break; } return new PaginationPerformanceMonitor(thresholds); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bcharleson/Instantly-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server