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);