diff --git a/public/index.html b/public/index.html
index 8f40c2c..3321a85 100644
--- a/public/index.html
+++ b/public/index.html
@@ -399,12 +399,13 @@
磁盘 |
网络 ↓ |
网络 ↑ |
+ Conntrack |
24h 流量 |
- | 暂无数据 - 请先配置 Prometheus 数据源 |
+ 暂无数据 - 请先配置 Prometheus 数据源 |
diff --git a/public/js/app.js b/public/js/app.js
index 9ef7b33..fcdacec 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1408,7 +1408,8 @@
` },
{ key: 'rootFsUsedPct', label: '根分区使用率 (/)', value: formatPercent(server.diskPercent) },
{ key: 'netRx', label: '网络接收速率 (RX)', value: formatBandwidth(server.netRx) },
- { key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(server.netTx) }
+ { key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(server.netTx) },
+ { key: 'conntrackUsedPct', label: 'Conntrack 占用比例', value: formatPercent(server.conntrackPercent) }
];
metrics.forEach(m => {
@@ -1502,6 +1503,10 @@
valA = (a.traffic24hRx ?? 0) + (a.traffic24hTx ?? 0);
valB = (b.traffic24hRx ?? 0) + (b.traffic24hTx ?? 0);
break;
+ case 'conntrack':
+ valA = a.conntrackPercent ?? 0;
+ valB = b.conntrackPercent ?? 0;
+ break;
default:
valA = (a.job || '').toLowerCase();
valB = (b.job || '').toLowerCase();
@@ -1627,7 +1632,7 @@
if (!servers || servers.length === 0) {
dom.serverTableBody.innerHTML = `
- | 暂无数据 - 请先配置 Prometheus 数据源 |
+ 暂无数据 - 请先配置 Prometheus 数据源 |
`;
return;
@@ -1672,6 +1677,14 @@
${formatBandwidth(server.netRx)} |
${formatBandwidth(server.netTx)} |
+
+
+
+ ${formatPercent(server.conntrackPercent || 0)}
+
+ |
${formatBytes((server.traffic24hRx || 0) + (server.traffic24hTx || 0))}
↓${formatBytes(server.traffic24hRx || 0)} / ↑${formatBytes(server.traffic24hTx || 0)}
@@ -1790,6 +1803,11 @@
{ key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(data.netTx) },
{ key: 'sockstatTcp', label: 'TCP 链接数 (Sockstat)', value: data.sockstatTcp.toFixed(0) },
{ key: 'sockstatTcpMem', label: 'TCP 内存占用', value: formatBytes(data.sockstatTcpMem) },
+ { key: 'conntrackUsedPct', label: 'Conntrack 占用比例', value: `
+
+ ${formatPercent(data.conntrackUsedPct)}
+ (${data.conntrackEntries.toFixed(0)} / ${data.conntrackLimit.toFixed(0)})
+ ` },
{ key: 'networkTrend', label: '网络流量趋势 (24h)', value: '' }
];
@@ -1952,6 +1970,7 @@
if (metricKey.includes('Pct') || metricKey === 'cpuBusy') unit = '%';
if (metricKey.startsWith('net')) unit = 'B/s';
if (metricKey === 'sockstatTcpMem') unit = 'B';
+ if (metricKey === 'conntrackUsedPct') unit = '%';
if (metricKey === 'networkTrend') {
chart = new AreaChart(canvas);
@@ -1966,6 +1985,7 @@
if (metricKey === 'memUsedPct') chart.totalValue = currentServerDetail.memTotal;
if (metricKey === 'swapUsedPct') chart.totalValue = currentServerDetail.swapTotal;
if (metricKey === 'rootFsUsedPct') chart.totalValue = currentServerDetail.rootFsTotal;
+ if (metricKey === 'conntrackUsedPct') chart.totalValue = data.conntrackLimit;
try {
const { instance, job, source } = currentServerDetail;
diff --git a/server/prometheus-service.js b/server/prometheus-service.js
index e324a65..4c721e6 100644
--- a/server/prometheus-service.js
+++ b/server/prometheus-service.js
@@ -211,7 +211,9 @@ async function getOverviewMetrics(url, sourceName) {
netTxResult,
netRx24hResult,
netTx24hResult,
- targetsResult
+ targetsResult,
+ conntrackEntriesResult,
+ conntrackLimitResult
] = await Promise.all([
// CPU usage per instance: 1 - avg idle
query(url, '100 - (avg by (instance, job) (rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)').catch(() => []),
@@ -234,7 +236,11 @@ async function getOverviewMetrics(url, sourceName) {
// 24h Network transmit total (bytes)
query(url, 'sum by (instance, job) (increase(node_network_transmit_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[24h]))').catch(() => []),
// Targets status from /api/v1/targets
- getTargets(url).catch(() => [])
+ getTargets(url).catch(() => []),
+ // Conntrack entries
+ query(url, 'node_nf_conntrack_entries').catch(() => []),
+ // Conntrack limits
+ query(url, 'node_nf_conntrack_entries_limit').catch(() => [])
]);
// Fetch 24h detailed traffic using the A*duration logic
@@ -270,9 +276,12 @@ async function getOverviewMetrics(url, sourceName) {
netTx: 0,
traffic24hRx: 0,
traffic24hTx: 0,
+ conntrackEntries: 0,
+ conntrackLimit: 0,
up: false,
memPercent: 0,
- diskPercent: 0
+ diskPercent: 0,
+ conntrackPercent: 0
});
}
const inst = instances.get(token);
@@ -348,6 +357,16 @@ async function getOverviewMetrics(url, sourceName) {
inst.traffic24hTx = parseFloat(r.value[1]) || 0;
}
+ // Parse conntrack
+ for (const r of conntrackEntriesResult) {
+ const inst = getOrCreate(r.metric);
+ inst.conntrackEntries = parseFloat(r.value[1]) || 0;
+ }
+ for (const r of conntrackLimitResult) {
+ const inst = getOrCreate(r.metric);
+ inst.conntrackLimit = parseFloat(r.value[1]) || 0;
+ }
+
for (const inst of instances.values()) {
if (!inst.up && (inst.cpuPercent > 0 || inst.memTotal > 0)) {
inst.up = true;
@@ -355,6 +374,7 @@ async function getOverviewMetrics(url, sourceName) {
// Calculate percentages on backend
inst.memPercent = inst.memTotal > 0 ? (inst.memUsed / inst.memTotal * 100) : 0;
inst.diskPercent = inst.diskTotal > 0 ? (inst.diskUsed / inst.diskTotal * 100) : 0;
+ inst.conntrackPercent = inst.conntrackLimit > 0 ? (inst.conntrackEntries / inst.conntrackLimit * 100) : 0;
}
const allInstancesList = Array.from(instances.values());
@@ -602,6 +622,9 @@ async function getServerDetails(baseUrl, instance, job, settings = {}) {
netTx: `sum(rate(node_network_transmit_bytes_total{instance="${node}",job="${job}",device!~'tap.*|veth.*|br.*|docker.*|virbr*|podman.*|lo.*|vmbr.*|fwbr.|ip.*|gre.*|virbr.*|vnet.*'}[1m]))`,
sockstatTcp: `node_sockstat_TCP_inuse{instance="${node}",job="${job}"}`,
sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096`,
+ conntrackEntries: `node_nf_conntrack_entries{instance="${node}",job="${job}"}`,
+ conntrackLimit: `node_nf_conntrack_entries_limit{instance="${node}",job="${job}"}`,
+ conntrackUsedPct: `(node_nf_conntrack_entries{instance="${node}",job="${job}"} / node_nf_conntrack_entries_limit{instance="${node}",job="${job}"}) * 100`,
// Get individual partitions (excluding virtual and FUSE mounts)
partitions_size: `node_filesystem_size_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|autofs|proc|sysfs|fuse.*", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*"}`,
partitions_free: `node_filesystem_free_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|autofs|proc|sysfs|fuse.*", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*"}`
@@ -771,7 +794,8 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
netRx: `sum(rate(node_network_receive_bytes_total{instance="${node}",job="${job}",device!~'tap.*|veth.*|br.*|docker.*|virbr*|podman.*|lo.*|vmbr.*|fwbr.|ip.*|gre.*|virbr.*|vnet.*'}[1m]))`,
netTx: `sum(rate(node_network_transmit_bytes_total{instance="${node}",job="${job}",device!~'tap.*|veth.*|br.*|docker.*|virbr*|podman.*|lo.*|vmbr.*|fwbr.|ip.*|gre.*|virbr.*|vnet.*'}[1m]))`,
sockstatTcp: `node_sockstat_TCP_inuse{instance="${node}",job="${job}"}`,
- sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096`
+ sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096`,
+ conntrackUsedPct: `(node_nf_conntrack_entries{instance="${node}",job="${job}"} / node_nf_conntrack_entries_limit{instance="${node}",job="${job}"}) * 100`
};
const rangeObj = parseRange(range, start, end);
|