Files
MyGoNavi/frontend/src/utils/jvmMonitoringPresentation.ts
Syngnat ff2b86819d feat(jvm-ui): 完善 JVM 工作台与监控入口
- 新增 JVM 持续监控仪表盘、图表、状态卡和详情面板

- 统一概览、资源浏览、审计页面的 JVM 工作台布局

- Sidebar 和 TabManager 支持监控入口、诊断入口兜底和上下文切换

- 补充前端状态模型、展示文案和组件回归测试
2026-04-26 14:34:02 +08:00

177 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
JVMMonitoringPoint,
JVMMonitoringRecentGCEvent,
JVMMonitoringSessionState,
} from "../types";
const METRIC_LABELS: Record<string, string> = {
"heap.used": "堆内存",
"heap.non_heap": "非堆内存",
"gc.count": "垃圾回收次数",
"gc.time": "垃圾回收耗时",
"gc.events": "最近垃圾回收事件",
"thread.count": "线程数",
"thread.states": "线程状态",
"class.loading": "类加载",
"cpu.process": "进程 CPU",
"cpu.system": "系统 CPU",
"memory.rss": "进程物理内存",
"memory.virtual": "进程虚拟内存",
};
export type JVMMonitoringProviderMode = JVMMonitoringSessionState["providerMode"];
const MONITORING_PROVIDER_MODES: JVMMonitoringProviderMode[] = [
"jmx",
"endpoint",
"agent",
];
const THREAD_STATE_LABELS: Record<string, string> = {
NEW: "新建",
RUNNABLE: "可运行",
BLOCKED: "阻塞",
WAITING: "等待中",
TIMED_WAITING: "限时等待",
TERMINATED: "已终止",
};
const timeFormatter = new Intl.DateTimeFormat("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
export type MonitoringChartPoint = JVMMonitoringPoint & {
timeLabel: string;
};
export const resolveMonitoringMetricLabel = (metric: string): string =>
METRIC_LABELS[String(metric || "").trim()] || String(metric || "").trim();
export const resolveThreadStateLabel = (state?: string | null): string => {
const normalized = String(state || "").trim().toUpperCase();
return THREAD_STATE_LABELS[normalized] || String(state || "").trim();
};
export const formatMonitoringTime = (timestamp?: number): string => {
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
return "--";
}
return timeFormatter.format(new Date(timestamp));
};
export const formatBytes = (value?: number): string => {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
return "--";
}
const units = ["B", "KB", "MB", "GB", "TB"];
let next = value;
let unitIndex = 0;
while (next >= 1024 && unitIndex < units.length - 1) {
next /= 1024;
unitIndex += 1;
}
const precision = next >= 100 || unitIndex === 0 ? 0 : next >= 10 ? 1 : 2;
return `${next.toFixed(precision)} ${units[unitIndex]}`;
};
export const formatMonitoringAxisBytes = (value?: number): string => formatBytes(value);
export const formatPercent = (value?: number): string => {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
return "--";
}
return `${(value * 100).toFixed(1)}%`;
};
export const formatCompactNumber = (value?: number): string => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return "--";
}
return value.toLocaleString("zh-CN");
};
export const formatDurationMs = (value?: number): string => {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
return "--";
}
return `${Math.round(value)}ms`;
};
export const normalizeMonitoringProviderMode = (
value: unknown,
fallback: JVMMonitoringProviderMode = "jmx",
): JVMMonitoringProviderMode => {
const normalized = String(value || "").trim().toLowerCase();
if (MONITORING_PROVIDER_MODES.includes(normalized as JVMMonitoringProviderMode)) {
return normalized as JVMMonitoringProviderMode;
}
return MONITORING_PROVIDER_MODES.includes(fallback) ? fallback : "jmx";
};
export const buildMonitoringAvailabilityText = ({
missingMetrics,
providerWarnings,
}: Pick<JVMMonitoringSessionState, "missingMetrics" | "providerWarnings">): string => {
const fragments: string[] = [];
if (Array.isArray(missingMetrics) && missingMetrics.length > 0) {
fragments.push(
`缺失指标:${missingMetrics
.map((metric) => resolveMonitoringMetricLabel(metric))
.join("、")}`,
);
}
if (Array.isArray(providerWarnings) && providerWarnings.length > 0) {
fragments.push(`监控来源告警:${providerWarnings.join("")}`);
}
if (fragments.length === 0) {
return "当前监控会话未发现明显降级。";
}
return fragments.join(" | ");
};
export const formatRecentGCLabel = (
event: JVMMonitoringRecentGCEvent,
): string => {
const parts = [
formatMonitoringTime(event.timestamp),
String(event.name || "").trim(),
typeof event.durationMs === "number" ? `${event.durationMs}ms` : "",
String(event.cause || "").trim(),
].filter(Boolean);
return parts.join(" · ");
};
export const buildMonitoringChartPoints = (
points: JVMMonitoringPoint[] = [],
): MonitoringChartPoint[] =>
points.map((point) => ({
...point,
timeLabel: formatMonitoringTime(point.timestamp),
}));
export const extractThreadStateRows = (
point?: JVMMonitoringPoint,
): Array<{ state: string; label: string; count: number }> =>
Object.entries(point?.threadStateCounts || {})
.map(([state, count]) => ({
state,
label: resolveThreadStateLabel(state),
count: Number(count) || 0,
}))
.sort((left, right) => right.count - left.count);
export const monitoringMetricAvailable = (
session: Pick<JVMMonitoringSessionState, "availableMetrics"> | undefined,
metric: string,
): boolean =>
Array.isArray(session?.availableMetrics) &&
session.availableMetrics.includes(metric);