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();
 | 
						|
  }
 | 
						|
}
 |