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