From 1a8c1b19a08f4bf8df24e714afb9936fe2360c43 Mon Sep 17 00:00:00 2001 From: LRcoding <38545827+LwClick@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:34:26 +0800 Subject: [PATCH] fix(mysql): preserve fractional dashboard rates --- .../src/components/admin/MySqlDashboard.vue | 10 +++---- .../database/mysqlServerStatus.spec.ts | 28 ++++++++++++++++++- .../src/lib/database/mysqlServerStatus.ts | 7 +++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/components/admin/MySqlDashboard.vue b/apps/desktop/src/components/admin/MySqlDashboard.vue index 2e90084603..c07eef3a62 100644 --- a/apps/desktop/src/components/admin/MySqlDashboard.vue +++ b/apps/desktop/src/components/admin/MySqlDashboard.vue @@ -9,7 +9,7 @@ import { useConnectionStore } from "@/stores/connectionStore"; import MetricCard from "@/components/common/MetricCard.vue"; import MetricLineChart from "@/components/chart/MetricLineChart.vue"; import * as api from "@/lib/backend/api"; -import { computeQps, computeRate, formatBytes, formatBytesPerSec, formatNumber, formatUptime, GLOBAL_STATUS_SQL, GLOBAL_VARIABLES_SQL, innodbBufferHitRatio, MAX_SAMPLES, parseStatusResult, statusEntries, statusNumber, type StatusSample } from "@/lib/database/mysqlServerStatus"; +import { computeQps, computeRate, formatBytes, formatBytesPerSec, formatNumber, formatRate, formatUptime, GLOBAL_STATUS_SQL, GLOBAL_VARIABLES_SQL, innodbBufferHitRatio, MAX_SAMPLES, parseStatusResult, statusEntries, statusNumber, type StatusSample } from "@/lib/database/mysqlServerStatus"; const props = defineProps<{ connectionId: string; @@ -195,7 +195,7 @@ onUnmounted(stopAutoRefresh);
- + @@ -206,10 +206,10 @@ onUnmounted(stopAutoRefresh);
- - + + - +
diff --git a/apps/desktop/src/lib/__tests__/database/mysqlServerStatus.spec.ts b/apps/desktop/src/lib/__tests__/database/mysqlServerStatus.spec.ts index f35585cb9d..fefa9deab2 100644 --- a/apps/desktop/src/lib/__tests__/database/mysqlServerStatus.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/mysqlServerStatus.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import type { QueryResult } from "@/types/database"; -import { computeQps, computeRate, connectionSupportsServerDashboard, formatBytes, formatBytesPerSec, formatUptime, innodbBufferHitRatio, parseStatusResult, statusNumber, supportsServerDashboard, type StatusSample } from "@/lib/database/mysqlServerStatus"; +import { computeQps, computeRate, connectionSupportsServerDashboard, formatBytes, formatBytesPerSec, formatNumber, formatRate, formatUptime, innodbBufferHitRatio, parseStatusResult, statusNumber, supportsServerDashboard, type StatusSample } from "@/lib/database/mysqlServerStatus"; function statusResult(rows: [string, string][]): QueryResult { return { columns: ["Variable_name", "Value"], rows, affected_rows: 0, execution_time_ms: 0 }; @@ -42,6 +42,14 @@ describe("computeRate", () => { expect(computeRate(prev, curr, "Bytes_sent")).toBe(2000); // 4000 bytes / 2s }); + it("preserves a fractional connection rate over a longer sample interval", () => { + const prev = sample(0, { Connections: "100" }); + const curr = sample(5000, { Connections: "101" }); + const rate = computeRate(prev, curr, "Connections"); + expect(rate).toBe(0.2); + expect(formatRate(rate)).toBe("0.2"); + }); + it("returns 0 on counter reset (decrease)", () => { const prev = sample(1000, { Queries: "9000" }); const curr = sample(2000, { Queries: "10" }); @@ -75,6 +83,24 @@ describe("innodbBufferHitRatio", () => { }); describe("formatters", () => { + it("formats rates with up to three fractional digits", () => { + expect(formatRate(0)).toBe("0"); + expect(formatRate(0.2)).toBe("0.2"); + expect(formatRate(0.125)).toBe("0.125"); + expect(formatRate(1 / 3)).toBe("0.333"); + expect(formatRate(1)).toBe("1"); + expect(formatRate(1000)).toBe("1,000"); + expect(formatRate(1234.5678)).toBe("1,234.568"); + expect(formatRate(Number.NaN)).toBe("0"); + expect(formatRate(Number.POSITIVE_INFINITY)).toBe("0"); + expect(formatRate(Number.NEGATIVE_INFINITY)).toBe("0"); + }); + + it("keeps integer metric formatting unchanged", () => { + expect(formatNumber(0.2)).toBe("0"); + expect(formatNumber(1234.6)).toBe("1,235"); + }); + it("formats bytes and bytes/sec", () => { expect(formatBytes(0)).toBe("0 B"); expect(formatBytes(1024)).toBe("1.0 KB"); diff --git a/apps/desktop/src/lib/database/mysqlServerStatus.ts b/apps/desktop/src/lib/database/mysqlServerStatus.ts index 6db97498bc..5aefedc58c 100644 --- a/apps/desktop/src/lib/database/mysqlServerStatus.ts +++ b/apps/desktop/src/lib/database/mysqlServerStatus.ts @@ -110,6 +110,13 @@ export function formatNumber(value: number): string { return Math.round(value).toLocaleString("en-US"); } +const RATE_NUMBER_FORMATTER = new Intl.NumberFormat("en-US", { maximumFractionDigits: 3 }); + +/** Cumulative-counter rates can be below 1/s, so preserve their fractional value. */ +export function formatRate(value: number): string { + return Number.isFinite(value) ? RATE_NUMBER_FORMATTER.format(value) : "0"; +} + const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"]; export function formatBytes(value: number): string {