queue and buffer refactor

This commit is contained in:
Alex Holliday committed 2026-01-16 18:11:04 +00:00
1 parent 0f764ca084
commit 95cd33ac06
10 files changed
+218 -287

No files matched your search

+86 -4
View File
@@ -1,5 +1,6 @@
import { IMonitorsRepository } from "@/repositories/index.js";
import type { MonitorType } from "@/types/index.js";
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
import type { MonitorType, MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
const SERVICE_NAME = "checkService";
@@ -11,32 +12,113 @@ class CheckService {
private stringService: any;
private errorService: any;
private monitorsRepository: IMonitorsRepository;
private checksRepository: IChecksRepository;
private logger: any;
constructor({
db,
settingsService,
stringService,
errorService,
monitorsRepository,
logger,
checksRepository,
}: {
db: any;
settingsService: any;
stringService: any;
errorService: any;
monitorsRepository: IMonitorsRepository;
logger: any;
checksRepository: IChecksRepository;
}) {
this.db = db;
this.settingsService = settingsService;
this.stringService = stringService;
this.errorService = errorService;
this.monitorsRepository = monitorsRepository;
this.logger = logger;
this.checksRepository = checksRepository;
}
get serviceName() {
return CheckService.SERVICE_NAME;
}
buildCheck = async (statusResponse: any, type: MonitorType) => {};
createChecks = async (checks: Check[]) => {
return this.checksRepository.createChecks(checks);
};
buildCheck = (statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>) => {
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
const check: Partial<Check> = {
metadata: {
monitorId,
teamId,
type,
},
status,
statusCode: code,
responseTime: responseTime || 0,
timings: timings,
message,
};
if (type === "pagespeed") {
const pageSpeedPayload = payload as PageSpeedStatusPayload | undefined;
if (!pageSpeedPayload) {
this.logger.warn({
message: "Failed to build check",
service: SERVICE_NAME,
method: "buildCheck",
details: "empty payload",
});
return undefined;
}
const categories = pageSpeedPayload.lighthouseResult?.categories ?? {};
const audits = pageSpeedPayload.lighthouseResult?.audits ?? {};
const mapAudit = (audit: any) => {
if (!audit || typeof audit !== "object") {
return undefined;
}
return {
id: audit.id,
title: audit.title,
score: typeof audit.score === "number" ? audit.score : (audit.score ?? null),
displayValue: audit.displayValue,
numericValue: typeof audit.numericValue === "number" ? audit.numericValue : undefined,
numericUnit: audit.numericUnit,
};
};
check.accessibility = (categories?.accessibility?.score || 0) * 100;
check.bestPractices = (categories?.["best-practices"]?.score || 0) * 100;
check.seo = (categories?.seo?.score || 0) * 100;
check.performance = (categories?.performance?.score || 0) * 100;
check.audits = {
cls: mapAudit(audits?.["cumulative-layout-shift"]),
si: mapAudit(audits?.["speed-index"]),
fcp: mapAudit(audits?.["first-contentful-paint"]),
lcp: mapAudit(audits?.["largest-contentful-paint"]),
tbt: mapAudit(audits?.["total-blocking-time"]),
};
}
if (type === "hardware") {
const hardwarePayload = payload as HardwareStatusPayload | undefined;
const { cpu, memory, disk, host, net } = hardwarePayload?.data ?? {};
const errorsSource = Array.isArray(hardwarePayload?.errors)
? hardwarePayload?.errors
: (hardwarePayload?.errors as { errors?: CheckErrorInfo[] } | undefined)?.errors;
check.cpu = cpu;
check.memory = memory;
check.disk = disk;
check.host = host;
check.errors = errorsSource;
check.capture = hardwarePayload?.capture;
check.net = net;
}
return check;
};
getChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: any; teamId: string }) => {
if (!monitorId) {
@@ -10,6 +10,8 @@ class SuperSimpleQueueHelper {
private networkService: INetworkService;
private statusService: any;
private notificationService: any;
private checkService: any;
private buffer: any;
constructor({
db,
@@ -17,18 +19,24 @@ class SuperSimpleQueueHelper {
networkService,
statusService,
notificationService,
checkService,
buffer,
}: {
db: any;
logger: any;
networkService: INetworkService;
statusService: any;
notificationService: any;
checkService: any;
buffer: any;
}) {
this.db = db;
this.logger = logger;
this.networkService = networkService;
this.statusService = statusService;
this.notificationService = notificationService;
this.checkService = checkService;
this.buffer = buffer;
}
get serviceName() {
@@ -61,7 +69,15 @@ class SuperSimpleQueueHelper {
throw new Error("No network response");
}
const statusChangeResult = await this.statusService.updateMonitorStatus(status);
// Step 3. Build check
const check = await this.checkService.buildCheck(status);
// Step 4 Add check to buffer
this.buffer.addToBuffer({ check });
// Step 4. Update monitor status
const statusChangeResult = await this.statusService.updateMonitorStatus(status, check);
this.notificationService
.handleNotifications({
...status,
@@ -1,15 +1,38 @@
import { config } from "@/config/index.js";
import type { Check } from "@/types/index.js";
const SERVICE_NAME = "BufferService";
class BufferService {
static SERVICE_NAME = SERVICE_NAME;
constructor({ db, logger, envSettings, incidentService }) {
console.log(envSettings);
private BUFFER_TIMEOUT: number;
private db: any;
private logger: any;
private incidentService: any;
private SERVICE_NAME: string;
private buffer: any[];
private incidentBuffer: any[];
private bufferTimer: NodeJS.Timeout;
private checksService: any;
constructor({
db,
logger,
envSettings,
incidentService,
checkService,
}: {
db: any;
logger: any;
envSettings: any;
incidentService: any;
checkService: any;
}) {
this.BUFFER_TIMEOUT = config.NODE_ENV === "development" ? 10 : 1000 * 60 * 1; // 1 minute
this.db = db;
this.logger = logger;
this.incidentService = incidentService;
this.checksService = checkService;
this.SERVICE_NAME = SERVICE_NAME;
this.buffer = [];
this.incidentBuffer = [];
@@ -25,10 +48,10 @@ class BufferService {
return BufferService.SERVICE_NAME;
}
addToBuffer({ check }) {
addToBuffer({ check }: { check: Check }) {
try {
this.buffer.push(check);
} catch (error) {
} catch (error: any) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
@@ -38,7 +61,7 @@ class BufferService {
}
}
addIncidentToBuffer({ monitor, check, action = "create" }) {
addIncidentToBuffer({ monitor, check, action = "create" }: { monitor: any; check: Check; action?: string }) {
try {
if (!monitor || !check) {
this.logger.warn({
@@ -50,7 +73,7 @@ class BufferService {
}
this.incidentBuffer.push({ monitor, check, action });
} catch (error) {
} catch (error: any) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
@@ -60,20 +83,20 @@ class BufferService {
}
}
removeCheckFromBuffer(checkToRemove) {
removeCheckFromBuffer(checkToRemove: Check) {
try {
if (!checkToRemove) {
return false;
}
const index = this.buffer.findIndex((check) => {
if (checkToRemove._id && check._id) {
return check._id.toString() === checkToRemove._id.toString();
if (checkToRemove.id && check.id) {
return check.id.toString() === checkToRemove.id.toString();
}
return (
check.monitorId?.toString() === checkToRemove.monitorId?.toString() &&
check.teamId?.toString() === checkToRemove.teamId?.toString() &&
check.type === checkToRemove.type &&
check.monitorId?.toString() === checkToRemove.metadata.monitorId &&
check.teamId?.toString() === checkToRemove.metadata.teamId &&
check.type === checkToRemove.metadata.type &&
check.status === checkToRemove.status &&
check.statusCode === checkToRemove.statusCode &&
check.responseTime === checkToRemove.responseTime &&
@@ -87,7 +110,7 @@ class BufferService {
}
return false;
} catch (error) {
} catch (error: any) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
@@ -102,7 +125,7 @@ class BufferService {
this.bufferTimer = setTimeout(async () => {
try {
await this.flushBuffer();
} catch (error) {
} catch (error: any) {
this.logger.error({
message: `Error in flush cycle: ${error.message}`,
service: this.SERVICE_NAME,
@@ -118,9 +141,9 @@ class BufferService {
async flushBuffer() {
try {
if (this.buffer.length > 0) {
await this.db.checkModule.createChecks(this.buffer);
await this.checksService.createChecks(this.buffer);
}
} catch (error) {
} catch (error: any) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
@@ -133,7 +156,7 @@ class BufferService {
if (this.incidentBuffer.length > 0 && this.incidentService) {
await this.flushIncidentBuffer();
}
} catch (error) {
} catch (error: any) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
@@ -154,7 +177,7 @@ class BufferService {
try {
const itemsToProcess = [...this.incidentBuffer];
await this.incidentService.processIncidentsFromBuffer(itemsToProcess);
} catch (error) {
} catch (error: any) {
this.logger.error({
message: `Error flushing incident buffer: ${error.message}`,
service: this.SERVICE_NAME,
@@ -1,8 +1,15 @@
import { IMonitorsRepository } from "@/repositories/index.js";
import MonitorStats from "../../db/models/MonitorStats.js";
import { CheckModel } from "@/db/models/index.js";
import type { CheckErrorInfo, Monitor, MonitorStatusResponse, StatusChangeResult } from "@/types/index.js";
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import type {
CheckErrorInfo,
Monitor,
MonitorStatusResponse,
StatusChangeResult,
Check,
HardwareStatusPayload,
PageSpeedStatusPayload,
} from "@/types/index.js";
const SERVICE_NAME = "StatusService";
class StatusService {
@@ -163,10 +170,9 @@ class StatusService {
};
updateMonitorStatus = async (
statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>
statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>,
check: Check
): Promise<StatusChangeResult> => {
const check = this.buildCheck(statusResponse);
await this.insertCheck(check);
try {
const { monitorId, teamId, status, code } = statusResponse;
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
@@ -301,116 +307,7 @@ class StatusService {
}
};
buildCheck = (
networkResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>
) => {
const {
monitorId,
teamId,
type,
status,
responseTime,
code,
message,
payload,
first_byte_took,
body_read_took,
dns_took,
conn_took,
connect_took,
tls_took,
timings,
} = networkResponse;
const check: any = {
metadata: {
monitorId,
teamId,
type,
},
status,
statusCode: code,
responseTime,
timings: timings || {},
message,
first_byte_took,
body_read_took,
dns_took,
conn_took,
connect_took,
tls_took,
};
if (type === "pagespeed") {
const pageSpeedPayload = payload as PageSpeedStatusPayload | undefined;
if (!pageSpeedPayload) {
this.logger.warn({
message: "Failed to build check",
service: SERVICE_NAME,
method: "buildCheck",
details: "empty payload",
});
return undefined;
}
const categories = pageSpeedPayload.lighthouseResult?.categories ?? {};
const audits = pageSpeedPayload.lighthouseResult?.audits ?? {};
const mapAudit = (audit: any) => {
if (!audit || typeof audit !== "object") {
return undefined;
}
return {
id: audit.id,
title: audit.title,
score: typeof audit.score === "number" ? audit.score : (audit.score ?? null),
displayValue: audit.displayValue,
numericValue: typeof audit.numericValue === "number" ? audit.numericValue : undefined,
numericUnit: audit.numericUnit,
};
};
check.accessibility = (categories?.accessibility?.score || 0) * 100;
check.bestPractices = (categories?.["best-practices"]?.score || 0) * 100;
check.seo = (categories?.seo?.score || 0) * 100;
check.performance = (categories?.performance?.score || 0) * 100;
check.audits = {
cls: mapAudit(audits?.["cumulative-layout-shift"]),
si: mapAudit(audits?.["speed-index"]),
fcp: mapAudit(audits?.["first-contentful-paint"]),
lcp: mapAudit(audits?.["largest-contentful-paint"]),
tbt: mapAudit(audits?.["total-blocking-time"]),
};
}
if (type === "hardware") {
const hardwarePayload = payload as HardwareStatusPayload | undefined;
const { cpu, memory, disk, host, net } = hardwarePayload?.data ?? {};
const errorsSource = Array.isArray(hardwarePayload?.errors)
? hardwarePayload?.errors
: (hardwarePayload?.errors as { errors?: CheckErrorInfo[] } | undefined)?.errors;
check.cpu = cpu ?? {};
check.memory = memory ?? {};
check.disk = disk ?? {};
check.host = host ?? {};
check.errors = errorsSource ?? [];
check.capture = hardwarePayload?.capture ?? {};
check.net = net ?? {};
}
return check;
};
/**
* Inserts a check into the database based on the network response.
*
* @param {Object} networkResponse - The network response object.
* @param {string} networkResponse.monitorId - The monitor ID.
* @param {string} networkResponse.type - The type of the response.
* @param {string} networkResponse.status - The status of the response.
* @param {number} networkResponse.responseTime - The response time.
* @param {number} networkResponse.code - The status code.
* @param {string} networkResponse.message - The message.
* @param {Object} networkResponse.payload - The payload of the response.
* @returns {Promise<void>} A promise that resolves when the check is inserted.
*/
insertCheck = async (check: any) => {
insertCheck = async (check: Check) => {
try {
if (typeof check === "undefined") {
this.logger.warn({
@@ -427,7 +324,7 @@ class StatusService {
message: error.message,
service: error.service || SERVICE_NAME,
method: error.method || "insertCheck",
details: error.details || `Error inserting check for monitor: ${check?.monitorId}`,
details: error.details || `Error inserting check for monitor: ${check?.metadata.monitorId}`,
stack: error.stack,
});
}