Skip to content

Commit 581de49

Browse files
authored
Merge pull request #63 from Yashh56/code-improvement
feat: Implement monitoring service with WebSocket support and UI enhancements
2 parents 57832a3 + ef6d165 commit 581de49

19 files changed

Lines changed: 1433 additions & 29 deletions

FEATURES.md

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This document provides a comprehensive breakdown of all features and capabilitie
1212
- [Core Pages and Navigation](#core-pages-and-navigation)
1313
- [Git Version Control](#git-version-control)
1414
- [Visual Tools](#visual-tools)
15+
- [Monitoring](#monitoring)
1516
- [UI and Design System](#ui-and-design-system)
1617
- [Integration and Architecture](#integration-and-architecture)
1718
- [Performance](#performance)
@@ -297,27 +298,13 @@ Interactive entity-relationship diagram visualization.
297298

298299
- ReactFlow-based interactive canvas
299300
- Automatic table node generation with column and type information
300-
- Primary key highlighting
301-
- Foreign key relationship lines between tables
302-
- Auto-layout algorithms
303301
- Minimap for large diagrams
304302
- Pan, zoom, and fit-to-screen controls
305-
- Drag to reposition tables
306-
- Hover tooltips for relationship details
307-
308-
---
309-
310303
### 7. Settings
311304

312-
Application appearance and preference management.
313-
314-
**Theme Mode**
315305

316306
- Light, dark, and system-preference (auto) modes
317-
- Real-time theme switching
318-
319307
**Accent Colors**
320-
321308
- Multiple color variants: Blue (default), Purple, Green, Pink, Orange, and more
322309
- Visual color preview with live UI element updates
323310
- Persistent preferences
@@ -435,6 +422,87 @@ RelWave includes native Git integration powered by `simple-git`, providing a ful
435422

436423
---
437424

425+
## Monitoring
426+
427+
RelWave includes a dedicated monitoring feature for live database and bridge health metrics. It provides low-latency charts, gauges, and streaming query insights to help diagnose performance and availability issues.
428+
429+
### The Architecture
430+
431+
- The Poller (Backend): a background worker queries the database every 2–5 seconds (configurable). Use a separate monitoring connection pool.
432+
- The Stream: metrics are pushed to the UI via WebSockets for real-time charts; HTTP polling is available as a fallback.
433+
- The Chart (Frontend): time-series charts (Recharts/ApexCharts) append incoming samples and support zoom/hover for details.
434+
435+
### 1. Application-Level Health Checks
436+
437+
- Add a `/health` or `/ping` endpoint that runs a lightweight query to confirm DB availability.
438+
439+
```sql
440+
SELECT 1;
441+
```
442+
443+
### 2. Database-Level Monitoring (Metrics & Request Counts)
444+
445+
- PostgreSQL exposes statistics via `pg_stat_database` and `pg_stat_activity` for live query and transaction counts.
446+
447+
Postgres example — total transactions:
448+
```sql
449+
SELECT xact_commit + xact_rollback AS total_transactions
450+
FROM pg_stat_database
451+
WHERE datname = 'your_db_name';
452+
```
453+
454+
MySQL/MariaDB example — queries counter:
455+
```sql
456+
SHOW GLOBAL STATUS LIKE 'Queries';
457+
```
458+
459+
### 3. Active Connections (Gauge)
460+
461+
Postgres:
462+
```sql
463+
SELECT
464+
(SELECT count(*) FROM pg_stat_activity) AS active_connections,
465+
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections;
466+
```
467+
468+
MySQL/MariaDB:
469+
```sql
470+
SHOW GLOBAL STATUS LIKE 'Threads_connected';
471+
SHOW VARIABLES LIKE 'max_connections';
472+
```
473+
474+
### 4. Database Throughput / QPS (Time-Series)
475+
476+
- Track total transaction counters over time and compute deltas to derive requests-per-second.
477+
478+
Example calculation: sample the `total_transactions` value every N seconds and compute `(current - previous) / N`.
479+
480+
### 5. Cache Hit Ratio (Health Percentage)
481+
482+
For Postgres the cache hit ratio is:
483+
$$\text{Cache Hit Ratio} = \left( \frac{\text{blks\_hit}}{\text{blks\_read} + \text{blks\_hit}} \right) \times 100$$
484+
485+
SQL example:
486+
```sql
487+
SELECT
488+
datname,
489+
round(100 * blks_hit / (blks_read + blks_hit + 1), 2) AS cache_hit_ratio
490+
FROM pg_stat_database
491+
WHERE datname = 'your_db_name';
492+
```
493+
494+
MySQL/MariaDB (InnoDB buffer pool): fetch `Innodb_buffer_pool_read_requests` and `Innodb_buffer_pool_reads` and compute:
495+
$$\text{Buffer Pool Hit Ratio} = \left( 1 - \frac{\text{Innodb\_buffer\_pool\_reads}}{\text{Innodb\_buffer\_pool\_read\_requests}} \right) \times 100$$
496+
497+
### Implementation Tips
498+
499+
- Use a separate connection pool for monitoring queries so UI monitoring doesn't starve application queries.
500+
- Default polling interval: 2–5 seconds; allow users to increase for low-impact polling.
501+
- Prefer WebSocket streaming for live dashboards; fallback to polling for environments without persistent sockets.
502+
- Monitoring queries are low-overhead (read internal stats) but should be rate-limited on large fleets.
503+
504+
---
505+
438506
## UI and Design System
439507

440508
### Layout Components
@@ -444,9 +512,9 @@ RelWave includes native Git integration powered by `simple-git`, providing a ful
444512
- Native minimize, maximize, and close buttons
445513
- Proper z-index layering
446514

447-
**Vertical Icon Bar**
515+
-**Vertical Icon Bar**
448516
- Fixed 60px left sidebar with navigation icons
449-
- Pages: Home, Settings, SQL Workspace, Query Builder, Schema Explorer, ER Diagram
517+
- Pages: Home, Settings, SQL Workspace, Query Builder, Schema Explorer, ER Diagram, Monitoring
450518
- Active state indicators and tooltip labels
451519

452520
**Slide-Out Panels**
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { describe, expect, test, jest, beforeEach } from "@jest/globals";
2+
import { MonitoringHandlers } from "../src/handlers/monitoringHandlers";
3+
import { DBType } from "../src/types";
4+
import type { Rpc } from "../src/types";
5+
6+
function createMockRpc(): Rpc & { _responses: any[]; _errors: any[] } {
7+
const responses: any[] = [];
8+
const errors: any[] = [];
9+
10+
return {
11+
sendResponse: jest.fn((id: number | string, payload: any) => {
12+
responses.push({ id, payload });
13+
}),
14+
sendError: jest.fn((id: number | string, err: any) => {
15+
errors.push({ id, err });
16+
}),
17+
_responses: responses,
18+
_errors: errors,
19+
};
20+
}
21+
22+
function createMockLogger(): any {
23+
return {
24+
info: jest.fn(),
25+
warn: jest.fn(),
26+
error: jest.fn(),
27+
debug: jest.fn(),
28+
child: jest.fn().mockReturnThis(),
29+
};
30+
}
31+
32+
function createMockDbService() {
33+
return {
34+
getDatabaseConnection: jest.fn(),
35+
};
36+
}
37+
38+
function createMockMonitoringService() {
39+
return {
40+
getSnapshot: jest.fn(),
41+
};
42+
}
43+
44+
describe("MonitoringHandlers", () => {
45+
let rpc: ReturnType<typeof createMockRpc>;
46+
let logger: any;
47+
let dbService: ReturnType<typeof createMockDbService>;
48+
let monitoringService: ReturnType<typeof createMockMonitoringService>;
49+
let handlers: MonitoringHandlers;
50+
51+
beforeEach(() => {
52+
rpc = createMockRpc();
53+
logger = createMockLogger();
54+
dbService = createMockDbService();
55+
monitoringService = createMockMonitoringService();
56+
handlers = new MonitoringHandlers(rpc, logger, dbService as any, monitoringService as any);
57+
});
58+
59+
test("returns BAD_REQUEST when db id is missing", async () => {
60+
await handlers.handleGetSnapshot({}, 1);
61+
62+
expect(rpc.sendError).toHaveBeenCalledWith(1, {
63+
code: "BAD_REQUEST",
64+
message: "Missing id",
65+
});
66+
expect(dbService.getDatabaseConnection).not.toHaveBeenCalled();
67+
});
68+
69+
test("returns monitoring snapshot for a valid database", async () => {
70+
const conn = { connection: true };
71+
const snapshot = {
72+
databaseType: DBType.POSTGRES,
73+
sampledAt: "2026-05-16T00:00:00.000Z",
74+
health: { ok: true, latencyMs: 12 },
75+
connections: { active: 2, max: 100, usagePct: 2 },
76+
throughput: { qps: 3.5, totalQueries: 1000 },
77+
cacheHitRatio: 99.2,
78+
activeQueries: [],
79+
};
80+
81+
(dbService.getDatabaseConnection as any).mockResolvedValue({ conn, dbType: DBType.POSTGRES });
82+
(monitoringService.getSnapshot as any).mockResolvedValue(snapshot);
83+
84+
await handlers.handleGetSnapshot({ id: "db-1" }, 2);
85+
86+
expect(dbService.getDatabaseConnection).toHaveBeenCalledWith("db-1");
87+
expect(monitoringService.getSnapshot).toHaveBeenCalledWith("db-1", conn, DBType.POSTGRES);
88+
expect(rpc.sendResponse).toHaveBeenCalledWith(2, {
89+
ok: true,
90+
data: snapshot,
91+
});
92+
});
93+
94+
test("returns MONITORING_ERROR when snapshot generation fails", async () => {
95+
(dbService.getDatabaseConnection as any).mockResolvedValue({ conn: {}, dbType: DBType.POSTGRES });
96+
(monitoringService.getSnapshot as any).mockRejectedValue(new Error("snapshot failed"));
97+
98+
await handlers.handleGetSnapshot({ id: "db-1" }, 3);
99+
100+
expect(logger.error).toHaveBeenCalled();
101+
expect(rpc.sendError).toHaveBeenCalledWith(3, {
102+
code: "MONITORING_ERROR",
103+
message: "snapshot failed",
104+
});
105+
});
106+
});

0 commit comments

Comments
 (0)