fix(mysql): preserve fractional dashboard rates
This commit is contained in:
parent
b9ac69f79a
commit
1a8c1b19a0
|
|
@ -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);
|
|||
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-3 p-3">
|
||||
<div class="grid shrink-0 grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<MetricCard :label="t('serverDashboard.qps')" :value="formatNumber(qps)" :icon="Gauge" />
|
||||
<MetricCard :label="t('serverDashboard.qps')" :value="formatRate(qps)" :icon="Gauge" />
|
||||
<MetricCard :label="t('serverDashboard.connections')" :value="`${formatNumber(threadsConnected)}${maxConnections ? ' / ' + formatNumber(maxConnections) : ''}`" :icon="Users" />
|
||||
<MetricCard :label="t('serverDashboard.running')" :value="formatNumber(threadsRunning)" :icon="Activity" />
|
||||
<MetricCard :label="t('serverDashboard.trafficIn')" :value="formatBytesPerSec(rate('Bytes_received'))" :icon="ArrowDownUp" />
|
||||
|
|
@ -206,10 +206,10 @@ onUnmounted(stopAutoRefresh);
|
|||
</div>
|
||||
|
||||
<div class="grid shrink-0 grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
<MetricLineChart :title="t('serverDashboard.qpsChart')" :labels="chartLabels" :series="qpsSeries" :value-formatter="formatNumber" />
|
||||
<MetricLineChart :title="t('serverDashboard.sessionsChart')" :labels="chartLabels" :series="sessionsSeries" :value-formatter="formatNumber" />
|
||||
<MetricLineChart :title="t('serverDashboard.qpsChart')" :labels="chartLabels" :series="qpsSeries" :value-formatter="formatRate" />
|
||||
<MetricLineChart :title="t('serverDashboard.sessionsChart')" :labels="chartLabels" :series="sessionsSeries" :value-formatter="formatRate" />
|
||||
<MetricLineChart :title="t('serverDashboard.trafficChart')" :labels="chartLabels" :series="trafficSeries" :value-formatter="formatBytes" />
|
||||
<MetricLineChart :title="t('serverDashboard.commandChart')" :labels="chartLabels" :series="commandSeries" :value-formatter="formatNumber" />
|
||||
<MetricLineChart :title="t('serverDashboard.commandChart')" :labels="chartLabels" :series="commandSeries" :value-formatter="formatRate" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col rounded-lg border bg-card" :class="showStatusTable ? 'min-h-0 flex-1' : 'shrink-0'">
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue