| import { RateLimitStore } from "./rate-limit-store"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export class SharedRateLimitMonitor { |
| private rateLimitStore: RateLimitStore; |
| private monitoringInterval?: NodeJS.Timeout; |
|
|
| |
| private readonly MAX_REQUESTS_PER_MINUTE = 20; |
| private readonly WINDOW_SIZE_MS = 60 * 1000; |
| private readonly MIN_REQUEST_INTERVAL_MS = 1000; |
|
|
| constructor() { |
| this.rateLimitStore = new RateLimitStore(); |
| } |
|
|
| |
| |
| |
| private cleanOldTimestamps(timestamps: number[]): number[] { |
| const now = Date.now(); |
| const cutoff = now - this.WINDOW_SIZE_MS; |
| return timestamps.filter((timestamp) => timestamp > cutoff); |
| } |
|
|
| |
| |
| |
| getCurrentStatus() { |
| const stored = this.rateLimitStore.read(); |
|
|
| if (!stored) { |
| |
| return { |
| requestsInCurrentWindow: 0, |
| maxRequestsPerMinute: this.MAX_REQUESTS_PER_MINUTE, |
| timeUntilWindowReset: this.WINDOW_SIZE_MS, |
| isCurrentlyLimited: false, |
| recommendedWaitTime: 0, |
| utilizationPercentage: 0, |
| timeUntilWindowResetMinutes: 1, |
| recommendedWaitTimeSeconds: 0, |
| dataSource: "default" as const, |
| lastUpdated: null, |
| }; |
| } |
|
|
| const now = Date.now(); |
| let requestsInWindow: number; |
| let timeUntilReset: number; |
|
|
| |
| if (stored.requestTimestamps) { |
| |
| const cleanTimestamps = this.cleanOldTimestamps(stored.requestTimestamps); |
| requestsInWindow = cleanTimestamps.length; |
|
|
| |
| const oldestTimestamp = cleanTimestamps[0]; |
| timeUntilReset = oldestTimestamp |
| ? Math.max(0, oldestTimestamp + this.WINDOW_SIZE_MS - now) |
| : 0; |
| } else { |
| |
| const windowElapsed = now - (stored.windowStart || 0); |
| requestsInWindow = stored.requestCount || 0; |
| timeUntilReset = this.WINDOW_SIZE_MS - windowElapsed; |
|
|
| if (windowElapsed >= this.WINDOW_SIZE_MS) { |
| requestsInWindow = 0; |
| timeUntilReset = this.WINDOW_SIZE_MS; |
| } |
| } |
|
|
| |
| const timeSinceLastRequest = now - stored.lastRequestTime; |
| const recommendedWait = Math.max( |
| 0, |
| this.MIN_REQUEST_INTERVAL_MS - timeSinceLastRequest |
| ); |
|
|
| const utilizationPercentage = |
| (requestsInWindow / this.MAX_REQUESTS_PER_MINUTE) * 100; |
|
|
| return { |
| requestsInCurrentWindow: requestsInWindow, |
| maxRequestsPerMinute: this.MAX_REQUESTS_PER_MINUTE, |
| timeUntilWindowReset: Math.max(0, timeUntilReset), |
| isCurrentlyLimited: stored.isLimited, |
| recommendedWaitTime: recommendedWait, |
| utilizationPercentage, |
| timeUntilWindowResetMinutes: Math.ceil( |
| Math.max(0, timeUntilReset) / 60000 |
| ), |
| recommendedWaitTimeSeconds: Math.ceil(recommendedWait / 1000), |
| dataSource: "shared" as const, |
| lastUpdated: new Date(stored.lastUpdated).toISOString(), |
| processId: stored.processId, |
| windowType: stored.requestTimestamps ? "sliding" : "fixed", |
| }; |
| } |
|
|
| |
| |
| |
| printStatus(clearConsole: boolean = false) { |
| if (clearConsole) { |
| |
| console.clear(); |
| } |
|
|
| const status = this.getCurrentStatus(); |
|
|
| const windowTypeIcon = |
| (status as any).windowType === "sliding" ? "π" : "β°"; |
| const windowTypeText = |
| (status as any).windowType === "sliding" |
| ? "Sliding Window" |
| : "Fixed Window"; |
|
|
| console.log(`\nπ DuckAI Rate Limit Status (${windowTypeText}):`); |
| console.log("ββββββββββββββββββββββββββββββββββββββββ"); |
| console.log( |
| `π Requests in current window: ${status.requestsInCurrentWindow}/${status.maxRequestsPerMinute}` |
| ); |
| console.log(`π Utilization: ${status.utilizationPercentage.toFixed(1)}%`); |
|
|
| if ((status as any).windowType === "sliding") { |
| console.log( |
| `${windowTypeIcon} Next request expires in: ${status.timeUntilWindowResetMinutes} minutes` |
| ); |
| } else { |
| console.log( |
| `${windowTypeIcon} Window resets in: ${status.timeUntilWindowResetMinutes} minutes` |
| ); |
| } |
|
|
| console.log( |
| `π¦ Currently limited: ${status.isCurrentlyLimited ? "β Yes" : "β
No"}` |
| ); |
|
|
| if (status.recommendedWaitTimeSeconds > 0) { |
| console.log( |
| `β³ Recommended wait: ${status.recommendedWaitTimeSeconds} seconds` |
| ); |
| } |
|
|
| |
| if (status.dataSource === "shared" && status.lastUpdated) { |
| const updateTime = new Date(status.lastUpdated).toLocaleTimeString(); |
| console.log(`π‘ Data from: Process ${status.processId} at ${updateTime}`); |
| } else { |
| console.log(`π‘ Data source: ${status.dataSource} (no active processes)`); |
| } |
|
|
| |
| const barLength = 20; |
| const filledLength = Math.round( |
| (status.utilizationPercentage / 100) * barLength |
| ); |
| const bar = "β".repeat(filledLength) + "β".repeat(barLength - filledLength); |
| console.log( |
| `π Usage: [${bar}] ${status.utilizationPercentage.toFixed(1)}%` |
| ); |
| console.log("ββββββββββββββββββββββββββββββββββββββββ\n"); |
| } |
|
|
| |
| |
| |
| printCompactStatus() { |
| const status = this.getCurrentStatus(); |
| const windowType = (status as any).windowType === "sliding" ? "π" : "β°"; |
| const limitIcon = status.isCurrentlyLimited ? "β" : "β
"; |
|
|
| console.log( |
| `${windowType} Rate Limit: ${status.requestsInCurrentWindow}/${status.maxRequestsPerMinute} (${status.utilizationPercentage.toFixed(1)}%) ${limitIcon}` |
| ); |
| } |
|
|
| |
| |
| |
| startMonitoring(intervalSeconds: number = 30) { |
| console.log( |
| `π Starting shared rate limit monitoring (every ${intervalSeconds}s)...` |
| ); |
| console.log(`π Store location: ${this.rateLimitStore.getStorePath()}`); |
| this.printStatus(); |
|
|
| this.monitoringInterval = setInterval(() => { |
| this.printStatus(true); |
| }, intervalSeconds * 1000); |
| } |
|
|
| |
| |
| |
| stopMonitoring() { |
| if (this.monitoringInterval) { |
| clearInterval(this.monitoringInterval); |
| this.monitoringInterval = undefined; |
| console.log("βΉοΈ Shared rate limit monitoring stopped."); |
| } |
| } |
|
|
| |
| |
| |
| getRecommendations() { |
| const status = this.getCurrentStatus(); |
| const recommendations: string[] = []; |
|
|
| if (status.dataSource === "default") { |
| recommendations.push( |
| "βΉοΈ No active DuckAI processes detected. Start making API calls to see real data." |
| ); |
| } |
|
|
| if (status.utilizationPercentage > 80) { |
| recommendations.push( |
| "β οΈ High utilization detected. Consider implementing request queuing." |
| ); |
| } |
|
|
| if (status.recommendedWaitTimeSeconds > 0) { |
| recommendations.push( |
| `β³ Wait ${status.recommendedWaitTimeSeconds}s before next request.` |
| ); |
| } |
|
|
| if (status.isCurrentlyLimited) { |
| recommendations.push( |
| "π« Currently rate limited. Wait for window reset or implement exponential backoff." |
| ); |
| } |
|
|
| if (status.utilizationPercentage < 50 && status.dataSource === "shared") { |
| recommendations.push( |
| "β
Good utilization level. You can safely increase request frequency." |
| ); |
| } |
|
|
| recommendations.push( |
| "π‘ Consider implementing request batching for better efficiency." |
| ); |
| recommendations.push("π Use exponential backoff for retry logic."); |
| recommendations.push("π Monitor rate limits continuously in production."); |
|
|
| return recommendations; |
| } |
|
|
| |
| |
| |
| printRecommendations() { |
| const recommendations = this.getRecommendations(); |
|
|
| console.log("\nπ‘ Rate Limit Recommendations:"); |
| console.log("ββββββββββββββββββββββββββββββββββββββββ"); |
| recommendations.forEach((rec) => console.log(rec)); |
| console.log("ββββββββββββββββββββββββββββββββββββββββ\n"); |
| } |
|
|
| |
| |
| |
| clearStore() { |
| this.rateLimitStore.clear(); |
| console.log("ποΈ Shared rate limit store cleared."); |
| } |
|
|
| |
| |
| |
| getStoreInfo() { |
| const stored = this.rateLimitStore.read(); |
| return { |
| storePath: this.rateLimitStore.getStorePath(), |
| hasData: !!stored, |
| data: stored, |
| }; |
| } |
| } |
|
|
| |
| if (require.main === module) { |
| const monitor = new SharedRateLimitMonitor(); |
|
|
| |
| const args = process.argv.slice(2); |
| const command = args[0]; |
|
|
| switch (command) { |
| case "status": |
| monitor.printStatus(); |
| monitor.printRecommendations(); |
| break; |
|
|
| case "monitor": |
| const interval = parseInt(args[1]) || 30; |
| monitor.startMonitoring(interval); |
|
|
| |
| process.on("SIGINT", () => { |
| monitor.stopMonitoring(); |
| process.exit(0); |
| }); |
| break; |
|
|
| case "clear": |
| monitor.clearStore(); |
| break; |
|
|
| case "info": |
| const info = monitor.getStoreInfo(); |
| console.log("π Store Information:"); |
| console.log(` Path: ${info.storePath}`); |
| console.log(` Has Data: ${info.hasData}`); |
| if (info.data) { |
| console.log( |
| ` Last Updated: ${new Date(info.data.lastUpdated).toLocaleString()}` |
| ); |
| console.log(` Process ID: ${info.data.processId}`); |
| console.log(` Requests: ${info.data.requestCount}`); |
| } |
| break; |
|
|
| default: |
| console.log("π DuckAI Shared Rate Limit Monitor"); |
| console.log(""); |
| console.log("This monitor reads rate limit data from a shared store,"); |
| console.log("showing real-time information across all DuckAI processes."); |
| console.log(""); |
| console.log("Usage:"); |
| console.log( |
| " bun run src/shared-rate-limit-monitor.ts status # Show current status" |
| ); |
| console.log( |
| " bun run src/shared-rate-limit-monitor.ts monitor [interval] # Start monitoring (default: 30s)" |
| ); |
| console.log( |
| " bun run src/shared-rate-limit-monitor.ts clear # Clear stored data" |
| ); |
| console.log( |
| " bun run src/shared-rate-limit-monitor.ts info # Show store info" |
| ); |
| console.log(""); |
| console.log("Examples:"); |
| console.log(" bun run src/shared-rate-limit-monitor.ts status"); |
| console.log(" bun run src/shared-rate-limit-monitor.ts monitor 10"); |
| console.log(" bun run src/shared-rate-limit-monitor.ts clear"); |
| break; |
| } |
| } |
|
|