Files
dbx/apps/desktop/src/lib/database/mysqlServerStatus.ts
T

84 lines
4.4 KiB
TypeScript

import type { ConnectionConfig, DatabaseType, QueryResult } from "@/types/database";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { computeRate, formatBytes, formatBytesPerSec, formatNumber, formatRate, formatUptime, statusEntries, statusNumber, type StatusEntry, type StatusMap, type StatusSample } from "@/lib/database/serverMetrics";
/**
* MySQL server-monitoring helpers. Pure and framework-free so the rate math and
* formatting can be unit-tested; the dashboard component owns the polling loop
* and ring buffer, and feeds samples through these functions.
*
* Data comes from `SHOW GLOBAL STATUS` (cumulative counters, two columns
* Variable_name/Value) and a one-shot `SHOW GLOBAL VARIABLES` (config such as
* max_connections/version), both run through the generic query bridge.
*
* Sample/rate math and formatting are engine-agnostic and live in
* `./serverMetrics`; re-exported here so existing callers keep one import path.
*/
export { computeRate, formatBytes, formatBytesPerSec, formatNumber, formatRate, formatUptime, statusEntries, statusNumber, type StatusEntry, type StatusMap, type StatusSample };
export const GLOBAL_STATUS_SQL = "SHOW GLOBAL STATUS";
export const GLOBAL_VARIABLES_SQL = "SHOW GLOBAL VARIABLES";
/** Max samples retained for the live charts (~ a few minutes at 5s cadence). */
export const MAX_SAMPLES = 60;
/**
* Engines with meaningful MySQL server-status counters (`SHOW GLOBAL STATUS`:
* QPS, Com_*, Bytes_*, InnoDB). MariaDB, TiDB, and OceanBase ride the `mysql`
* dbType via a driver profile, so they are covered by the `"mysql"` entry.
*
* Deliberately excludes the OLAP MySQL-protocol forks (Doris, StarRocks) — they
* have no InnoDB and do not expose these status counters, so the dashboard would
* be empty there. GoldenDB is excluded pending confirmation its proxy passes the
* counters through.
*/
const SERVER_DASHBOARD_DB_TYPES = new Set<DatabaseType>(["mysql"]);
/** Parse a two-column `SHOW GLOBAL STATUS` / `SHOW GLOBAL VARIABLES` result. */
export function parseStatusResult(result: QueryResult | null | undefined): StatusMap {
const map: StatusMap = {};
if (!result || !Array.isArray(result.columns) || !Array.isArray(result.rows)) return map;
const nameIdx = result.columns.findIndex((c) => c.toLowerCase() === "variable_name");
const valueIdx = result.columns.findIndex((c) => c.toLowerCase() === "value");
const nameCol = nameIdx >= 0 ? nameIdx : 0;
const valueCol = valueIdx >= 0 ? valueIdx : 1;
for (const row of result.rows) {
const name = row[nameCol];
if (name === null || name === undefined) continue;
map[String(name)] = row[valueCol] === null || row[valueCol] === undefined ? "" : String(row[valueCol]);
}
return map;
}
/** QPS between two samples, preferring `Queries` and falling back to `Questions`. */
export function computeQps(prev: StatusSample, curr: StatusSample): number {
const hasQueries = curr.status.Queries !== undefined;
return computeRate(prev, curr, hasQueries ? "Queries" : "Questions");
}
/**
* InnoDB buffer pool hit ratio (0-100) from cumulative reads vs read requests.
* Returns null when the counters are unavailable (non-InnoDB / forks).
*/
export function innodbBufferHitRatio(status: StatusMap): number | null {
const requests = statusNumber(status, "Innodb_buffer_pool_read_requests");
if (requests <= 0) return null;
const reads = statusNumber(status, "Innodb_buffer_pool_reads");
const ratio = (1 - reads / requests) * 100;
if (!Number.isFinite(ratio)) return null;
return Math.max(0, Math.min(100, ratio));
}
/** Whether the given database type exposes the server dashboard (MySQL family). */
export function supportsServerDashboard(dbType: DatabaseType | undefined): boolean {
return !!dbType && SERVER_DASHBOARD_DB_TYPES.has(dbType);
}
/** Prevent JDBC profiles that only borrow MySQL SQL syntax from exposing MySQL server administration queries. */
export function connectionSupportsServerDashboard(connection: ConnectionConfig | undefined): boolean {
if (!connection || !supportsServerDashboard(effectiveDatabaseTypeForConnection(connection))) return false;
if (connection.db_type !== "jdbc") return true;
const profile = [connection.driver_profile, connection.connection_string, connection.jdbc_driver_class, ...(connection.jdbc_driver_paths ?? [])].filter(Boolean).join("\n");
return !/(?:kyuubi|hive2|org\.apache\.hive\.jdbc\.HiveDriver|hive-jdbc)/i.test(profile);
}