diff --git a/client/src/Pages/Logs/components/MongoStats.tsx b/client/src/Pages/Logs/components/MongoStats.tsx
index ed7b9cc53..8dc2f0231 100644
--- a/client/src/Pages/Logs/components/MongoStats.tsx
+++ b/client/src/Pages/Logs/components/MongoStats.tsx
@@ -33,6 +33,26 @@ export const MongoStats = ({ diagnostics }: MongoStatsProps) => {
title={t("pages.logs.diagnostics.mongoDBStats.readyState")}
subtitle={mongoStats.readyState.toString()}
/>
+
+
+
+
+
) {
/**
* 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,
diff --git a/server/test/unit/services/diagnosticService.test.ts b/server/test/unit/services/diagnosticService.test.ts
index b44de1986..07962a68e 100644
--- a/server/test/unit/services/diagnosticService.test.ts
+++ b/server/test/unit/services/diagnosticService.test.ts
@@ -104,9 +104,8 @@ describe("DiagnosticService", () => {
it("returns only the slimmed diagnostic shape (no serverStatus/serverInfo/memoryUsage)", async () => {
const service = new DiagnosticService(makeDb());
const promise = service.getSystemStats();
- // getCPUUsagePercentage waits 1s, then event loop delay waits 0ms
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ // getCPUUsagePercentage waits 1s, the first mongo ops sample waits another 1s, then event loop delay waits 0ms
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
expect(Object.keys(result).sort()).toEqual(["cpuUsage", "eventLoopDelayMs", "mongoStats", "osStats", "uptimeMs", "v8HeapStats"].sort());
@@ -131,11 +130,24 @@ describe("DiagnosticService", () => {
});
const service = new DiagnosticService(db);
const promise = service.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
- expect(Object.keys(result.mongoStats).sort()).toEqual(["collections", "dbName", "host", "port", "readyState", "totalSize"].sort());
+ expect(Object.keys(result.mongoStats).sort()).toEqual(
+ [
+ "collections",
+ "dbName",
+ "host",
+ "port",
+ "readyState",
+ "totalSize",
+ "readsPerSecond",
+ "insertsPerSecond",
+ "updatesPerSecond",
+ "deletesPerSecond",
+ "writesPerSecond",
+ ].sort()
+ );
expect(result.mongoStats.totalSize).toBe(9999);
expect(result.mongoStats.collections).toHaveLength(2);
expect(result.mongoStats.collections[0]).toEqual({
@@ -154,8 +166,7 @@ describe("DiagnosticService", () => {
});
const service = new DiagnosticService(db);
const promise = service.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
expect(result.mongoStats.totalSize).toBe(376832 + 1155072);
@@ -169,8 +180,7 @@ describe("DiagnosticService", () => {
});
const service = new DiagnosticService(db);
const promise = service.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
const coll = result.mongoStats.collections[0];
@@ -190,8 +200,7 @@ describe("DiagnosticService", () => {
});
const service = new DiagnosticService(db);
const promise = service.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
expect(result.mongoStats.collections[0]?.bucketCount).toBe(12);
@@ -199,8 +208,7 @@ describe("DiagnosticService", () => {
const db2 = makeDb({ collections: [{ collectionName: "monitors" }] });
const service2 = new DiagnosticService(db2);
const promise2 = service2.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result2 = await promise2;
expect(result2.mongoStats.collections[0]).not.toHaveProperty("bucketCount");
});
@@ -235,8 +243,7 @@ describe("DiagnosticService", () => {
performance.getEntriesByName = jest.fn().mockReturnValue(entries) as any;
const promise = service.getSystemStats();
- jest.advanceTimersByTime(1000);
- await jest.advanceTimersByTimeAsync(10);
+ await jest.advanceTimersByTimeAsync(2500);
const result = await promise;
expect(result.eventLoopDelayMs).toBe(0);