138 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * PerformanceMonitor - Track latencies, errors, and cache metrics
 | |
|  * 
 | |
|  * Features:
 | |
|  * - Request timing (avg, p95)
 | |
|  * - Cache hit/miss tracking
 | |
|  * - Error counting
 | |
|  * - Simple ring buffer for timings
 | |
|  */
 | |
| 
 | |
| export class PerformanceMonitor {
 | |
|   constructor(clock = () => Date.now()) {
 | |
|     this.clock = clock;
 | |
|     this.counters = {
 | |
|       cacheHit: 0,
 | |
|       cacheMiss: 0,
 | |
|       reqCount: 0,
 | |
|       reqError: 0,
 | |
|       meilisearchRetry: 0,
 | |
|       filesystemRetry: 0
 | |
|     };
 | |
|     this.timings = []; // Ring buffer for latencies
 | |
|     this.maxTimings = 500;
 | |
|     this.startTime = Date.now();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Mark request start, returns timestamp
 | |
|    */
 | |
|   markRequestStart() {
 | |
|     return this.clock();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Mark request end, calculate duration
 | |
|    * Returns duration in ms
 | |
|    */
 | |
|   markRequestEnd(startTs, ok = true) {
 | |
|     const dur = this.clock() - startTs;
 | |
|     
 | |
|     if (this.timings.length >= this.maxTimings) {
 | |
|       this.timings.shift();
 | |
|     }
 | |
|     this.timings.push(dur);
 | |
|     
 | |
|     this.counters.reqCount += 1;
 | |
|     if (!ok) this.counters.reqError += 1;
 | |
|     
 | |
|     return dur;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Mark cache hit or miss
 | |
|    */
 | |
|   markCache(hit) {
 | |
|     if (hit) {
 | |
|       this.counters.cacheHit += 1;
 | |
|     } else {
 | |
|       this.counters.cacheMiss += 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Mark retry event
 | |
|    */
 | |
|   markRetry(source = 'unknown') {
 | |
|     if (source === 'meilisearch') {
 | |
|       this.counters.meilisearchRetry += 1;
 | |
|     } else if (source === 'filesystem') {
 | |
|       this.counters.filesystemRetry += 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get performance snapshot
 | |
|    */
 | |
|   snapshot() {
 | |
|     const arr = this.timings.slice();
 | |
|     const n = arr.length || 1;
 | |
|     const sum = arr.reduce((a, b) => a + b, 0);
 | |
|     const avg = sum / n;
 | |
|     
 | |
|     // Calculate p95
 | |
|     const sorted = arr.slice().sort((a, b) => a - b);
 | |
|     const p95Idx = Math.min(n - 1, Math.floor(n * 0.95));
 | |
|     const p95 = sorted[p95Idx] || 0;
 | |
| 
 | |
|     const uptime = this.clock() - this.startTime;
 | |
|     const cacheTotal = this.counters.cacheHit + this.counters.cacheMiss;
 | |
|     const cacheHitRate = cacheTotal > 0 
 | |
|       ? Math.round((this.counters.cacheHit / cacheTotal) * 1000) / 10
 | |
|       : 0;
 | |
| 
 | |
|     const errorRate = this.counters.reqCount > 0
 | |
|       ? Math.round((this.counters.reqError / this.counters.reqCount) * 1000) / 10
 | |
|       : 0;
 | |
| 
 | |
|     return {
 | |
|       uptime,
 | |
|       requests: {
 | |
|         total: this.counters.reqCount,
 | |
|         errors: this.counters.reqError,
 | |
|         errorRate: `${errorRate}%`
 | |
|       },
 | |
|       cache: {
 | |
|         hits: this.counters.cacheHit,
 | |
|         misses: this.counters.cacheMiss,
 | |
|         hitRate: `${cacheHitRate}%`
 | |
|       },
 | |
|       retries: {
 | |
|         meilisearch: this.counters.meilisearchRetry,
 | |
|         filesystem: this.counters.filesystemRetry
 | |
|       },
 | |
|       latency: {
 | |
|         avgMs: Math.round(avg),
 | |
|         p95Ms: Math.round(p95),
 | |
|         samples: this.timings.length
 | |
|       }
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Reset all counters
 | |
|    */
 | |
|   reset() {
 | |
|     this.counters = {
 | |
|       cacheHit: 0,
 | |
|       cacheMiss: 0,
 | |
|       reqCount: 0,
 | |
|       reqError: 0,
 | |
|       meilisearchRetry: 0,
 | |
|       filesystemRetry: 0
 | |
|     };
 | |
|     this.timings = [];
 | |
|     this.startTime = this.clock();
 | |
|   }
 | |
| }
 |