From ad9ff7f5df05613803483ee393ac76577196720e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 17 Jun 2026 10:55:39 -0700 Subject: [PATCH] add mongo read/write per second stats --- server/scripts/seed-monitors.js | 2 +- .../domain/diagnostics/diagnostic.service.ts | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/server/scripts/seed-monitors.js b/server/scripts/seed-monitors.js index e8419f73f..f62fe2eb3 100644 --- a/server/scripts/seed-monitors.js +++ b/server/scripts/seed-monitors.js @@ -14,7 +14,7 @@ import { UserModel } from "../dist/domain/users/user.model.js"; // NOTE: the running queue only schedules monitors it finds at init(), so restart // the server after seeding for the new monitors to actually start checking. -const DEFAULT_COUNT = 5_000; +const DEFAULT_COUNT = 500; const DEFAULT_BATCH = 1_000; const DEFAULT_INTERVAL = 60_000; const NAME_PREFIX = "Seed"; diff --git a/server/src/domain/diagnostics/diagnostic.service.ts b/server/src/domain/diagnostics/diagnostic.service.ts index 408b19cb5..f26fa02e3 100644 --- a/server/src/domain/diagnostics/diagnostic.service.ts +++ b/server/src/domain/diagnostics/diagnostic.service.ts @@ -6,6 +6,22 @@ import { AppError } from "@/utils/AppError.js"; const SERVICE_NAME = "diagnosticService"; +export interface MongoStats { + reads: number; + inserts: number; + updates: number; + deletes: number; + timestamp: number; +} + +export interface MongoOpsPerSecond { + readsPerSecond: number; + insertsPerSecond: number; + updatesPerSecond: number; + deletesPerSecond: number; + writesPerSecond: number; +} + export interface CollectionDiagnostics { name: string; documentCount: number; @@ -17,6 +33,11 @@ export interface CollectionDiagnostics { export interface MongoDiagnostics { readyState: number; + readsPerSecond: number; + insertsPerSecond: number; + updatesPerSecond: number; + deletesPerSecond: number; + writesPerSecond: number; host: string; port: number; dbName: string; @@ -44,6 +65,8 @@ export interface IDiagnosticService { export class DiagnosticService implements IDiagnosticService { static SERVICE_NAME = SERVICE_NAME; + private lastMongoOpsPerSecond: MongoStats | null = null; + constructor(private db: IDb) { /** * Performance Observer for monitoring system performance metrics. @@ -68,6 +91,48 @@ export class DiagnosticService implements IDiagnosticService { return ((endUsage.user + endUsage.system) / 1000 / timingPeriod) * 100; }; + private getMongoOpsPerSecond = async (): Promise => { + try { + const mongo = await this.db.getConnection(); + const db = mongo.connection.db; + if (!db) { + throw new AppError({ message: "Database connection is not available", service: SERVICE_NAME, method: "getMongoDBStats" }); + } + const sample = async (): Promise => { + const res = await db.command({ serverStatus: 1 }); + return { + reads: (res.opcounters?.query ?? 0) + (res.opcounters?.getmore ?? 0), + inserts: res.opcounters?.insert ?? 0, + updates: res.opcounters?.update ?? 0, + deletes: res.opcounters?.delete ?? 0, + timestamp: Date.now(), + }; + }; + + // First call has no baseline, seed one so we always have a rate + if (this.lastMongoOpsPerSecond === null) { + this.lastMongoOpsPerSecond = await sample(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + const previous = this.lastMongoOpsPerSecond; + const current = await sample(); + this.lastMongoOpsPerSecond = current; + + const elapsedSeconds = (current.timestamp - previous.timestamp) / 1000; + return { + readsPerSecond: (current.reads - previous.reads) / elapsedSeconds, + insertsPerSecond: (current.inserts - previous.inserts) / elapsedSeconds, + updatesPerSecond: (current.updates - previous.updates) / elapsedSeconds, + deletesPerSecond: (current.deletes - previous.deletes) / elapsedSeconds, + writesPerSecond: + (current.inserts - previous.inserts + current.updates - previous.updates + current.deletes - previous.deletes) / elapsedSeconds, + }; + } catch { + return null; + } + }; + private getMongoDBStats = async (): Promise => { const mongo = await this.db.getConnection(); const db = mongo.connection.db; @@ -75,6 +140,8 @@ export class DiagnosticService implements IDiagnosticService { throw new AppError({ message: "Database connection is not available", service: SERVICE_NAME, method: "getMongoDBStats" }); } + const opsPerSecond = await this.getMongoOpsPerSecond(); + const dbStats = await db.stats(); const rawCollections = await db.collections(); @@ -100,6 +167,11 @@ export class DiagnosticService implements IDiagnosticService { return { readyState: mongo.connection.readyState, + readsPerSecond: opsPerSecond?.readsPerSecond ?? 0, + insertsPerSecond: opsPerSecond?.insertsPerSecond ?? 0, + updatesPerSecond: opsPerSecond?.updatesPerSecond ?? 0, + deletesPerSecond: opsPerSecond?.deletesPerSecond ?? 0, + writesPerSecond: opsPerSecond?.writesPerSecond ?? 0, host: mongo.connection.host, port: mongo.connection.port, dbName: mongo.connection.name,