feat: add observability buffer for heartbeat data

- Introduced `ObservabilityBufferStore` to manage buffered observability records.
- Implemented methods for upserting, replaying, and acknowledging records in the buffer.
- Enhanced `AgentNodePayload` and `NodePayload` to include buffered observability data.
- Updated tests to cover new functionality for observability buffering.
- Modified existing services to persist and handle buffered observability data during heartbeats.
- Added configuration options for observability buffer path and replay minutes.
- Updated documentation to reflect new observability features and configurations.
This commit is contained in:
ryan
2026-03-14 17:57:50 +08:00
parent 7628397785
commit e25b41fd75
18 changed files with 798 additions and 64 deletions
@@ -43,6 +43,13 @@ function formatBytes(value?: number | null) {
return `${current.toFixed(current >= 100 || index === 0 ? 0 : 1)} ${units[index]}`;
}
function formatBytesPerSecond(value?: number | null, windowSeconds = 1) {
if (!value || value <= 0 || windowSeconds <= 0) {
return '—';
}
return `${formatBytes(value / windowSeconds)}/s`;
}
function getErrorMessage(error: unknown) {
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
}
@@ -281,7 +288,7 @@ export function DashboardOverview() {
values: overview.trends.network_24h.map(
(point) => point.openresty_rx_bytes,
),
valueFormatter: formatBytes,
valueFormatter: (value) => formatBytesPerSecond(value, 3600),
},
{
label: 'OpenResty 出站',
@@ -289,7 +296,7 @@ export function DashboardOverview() {
values: overview.trends.network_24h.map(
(point) => point.openresty_tx_bytes,
),
valueFormatter: formatBytes,
valueFormatter: (value) => formatBytesPerSecond(value, 3600),
},
]}
/>
@@ -89,6 +89,13 @@ function formatBytes(value?: number | null) {
return `${current.toFixed(current >= 100 || index === 0 ? 0 : 1)} ${units[index]}`;
}
function formatBytesPerSecond(value?: number | null, windowSeconds = 1) {
if (!value || value <= 0 || windowSeconds <= 0) {
return '—';
}
return `${formatBytes(value / windowSeconds)}/s`;
}
function formatPercent(value?: number | null) {
if (value === undefined || value === null || Number.isNaN(value)) {
return '—';
@@ -929,11 +936,17 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
<div className="mt-3 space-y-2 text-sm text-[var(--foreground-secondary)]">
<p>
入站:
{formatBytes(latestMetricSnapshot.openresty_rx_bytes)}
{formatBytesPerSecond(
latestMetricSnapshot.openresty_rx_bytes,
60,
)}
</p>
<p>
出站:
{formatBytes(latestMetricSnapshot.openresty_tx_bytes)}
{formatBytesPerSecond(
latestMetricSnapshot.openresty_tx_bytes,
60,
)}
</p>
</div>
</div>
@@ -1087,7 +1100,7 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
observability?.trends.network_24h.map(
(point) => point.openresty_rx_bytes,
) ?? [],
valueFormatter: formatBytes,
valueFormatter: (value) => formatBytesPerSecond(value, 3600),
},
{
label: 'OpenResty 出站',
@@ -1096,7 +1109,7 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
observability?.trends.network_24h.map(
(point) => point.openresty_tx_bytes,
) ?? [],
valueFormatter: formatBytes,
valueFormatter: (value) => formatBytesPerSecond(value, 3600),
},
]}
/>