@aj-archipelago/cortex
Version:
Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.
156 lines (132 loc) • 4.09 kB
JavaScript
import { v4 as uuidv4 } from 'uuid';
import { Deque } from '@datastructures-js/deque';
class RequestMonitor {
constructor( callsToKeep = 10 ) {
this.callCount = new Deque();
this.peakCallRate = 0;
this.error429Count = new Deque();
this.errorCount = new Deque();
this.startTime = new Date();
this.callStartTimes = new Map();
this.callDurations = new Deque();
this.healthy = true;
this.ageOutTime = 5 * 60 * 1000; // 5 minutes
this.callsToKeep = callsToKeep;
}
get isHealthy() {
return this.healthy;
}
removeOldCallStarts() {
const currentTime = new Date();
for (const [callId, startTime] of this.callStartTimes) {
if (currentTime - startTime > this.ageOutTime) {
this.callStartTimes.delete(callId);
}
}
}
removeOldCallStats(dq, timeProperty) {
const currentTime = new Date();
while (!dq.isEmpty() && currentTime - (timeProperty ? dq.front()[timeProperty] : dq.front()) > this.ageOutTime) {
dq.popFront();
}
}
maintain() {
this.removeOldCallStarts();
this.removeOldCallStats(this.callCount);
if (this.callCount.size() === 0) {
this.peakCallRate = 0;
}
this.removeOldCallStats(this.callDurations, 'endTime');
this.removeOldCallStats(this.error429Count);
this.removeOldCallStats(this.errorCount);
if (this.getErrorRate() > 0.1) {
this.healthy = false;
} else {
this.healthy = true;
}
}
startCall() {
const callId = uuidv4();
const currentTime = new Date();
this.callStartTimes.set(callId, currentTime);
this.callCount.pushBack(currentTime);
this.maintain();
return callId;
}
endCall(callId) {
const endTime = new Date();
const startTime = this.callStartTimes.get(callId);
let callDuration = null;
if (startTime) {
callDuration = (endTime - startTime);
this.callStartTimes.delete(callId);
this.callDurations.pushBack({endTime, callDuration});
// Keep the callDurations length to 5
while (this.callDurations.size() > this.callsToKeep) {
this.callDurations.popFront();
}
}
const callRate = this.getCallRate();
if (callRate > this.peakCallRate) {
this.peakCallRate = callRate;
}
this.maintain();
return callDuration;
}
getAverageCallDuration() {
this.maintain();
if (this.callDurations.size() === 0) {
return 0;
}
const sum = this.callDurations.toArray().reduce((a, b) => a + b.callDuration, 0);
return sum / this.callDurations.size();
}
incrementErrorCount(callId, status) {
this.errorCount.pushBack(new Date());
if (status === 429) {
this.error429Count.pushBack(new Date());
}
this.maintain();
return callId ? this.endCall(callId) : null;
}
getCallRate() {
this.maintain();
const currentTime = new Date();
const timeElapsed = (currentTime - this.callCount.front()) / 1000; // time elapsed in seconds]
return timeElapsed < 1 ? this.callCount.size() : this.callCount.size() / timeElapsed;
}
getPeakCallRate() {
this.maintain();
return this.peakCallRate;
}
getError429Rate() {
return this.callCount.size() ? this.error429Count.size() / this.callCount.size() : 0;
}
getErrorRate() {
return this.callCount.size() ? this.errorCount.size() / this.callCount.size() : 0;
}
calculatePercentComplete(callId) {
if (!this.callDurations.size()) {
return 0;
}
const currentTime = new Date();
const duration = currentTime - this.callStartTimes.get(callId);
const average = this.getAverageCallDuration();
let percentComplete = duration / average;
if (percentComplete > 0.8) {
percentComplete = 0.8;
}
return percentComplete;
}
reset() {
this.callCount.clear();
this.peakCallRate = 0;
this.error429Count.clear();
this.errorCount.clear();
this.startTime = new Date();
this.callStartTimes = new Map();
this.callDurations.clear();
this.healthy = true;
}
}
export default RequestMonitor;