mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
feat(obs): 去掉宿主机网卡趋势,磁盘读写改按速率展示
Agent 不再采集网卡累计字节,看板与节点网络图仅保留访问日志已提供/接收。 磁盘 IO 按小时换算为 B/s 曲线,摘要为近 24 小时平均速率,并同步 Swagger。
This commit is contained in:
@@ -44,6 +44,8 @@ sidebar: false
|
||||
- Agent 二进制替换升级时,若本地观测补传缓冲仍是旧格式或损坏,则直接删除该文件并在运行中重建,避免半迁移数据或阻塞心跳。
|
||||
- Zone 流量图:顶部独立访客为整窗去重;曲线标明为分桶 UV(桶内去重,不可跨桶相加)。
|
||||
- 明确 OpenResty 健康权威:当前 status/message 以节点表为准,ClickHouse 仅存 status 与连接时序(不含 message)。
|
||||
- 看板与节点观测不再展示或采集宿主机网卡入/出站;网络趋势仅保留访问日志的已提供/接收数据。
|
||||
- 宿主机磁盘读/写趋势改为按小时速率(B/s)展示,摘要为近 24 小时平均速率。
|
||||
- Pages 部署包上传体积限制改为系统动态配置,默认仍为 100 MiB,可按环境调整。
|
||||
- 上传新部署后会按保留策略自动清理超出数量的历史部署包,减少磁盘占用。
|
||||
- Agent 在同步 Pages 部署时信任控制面已完成的包校验,不再重复限制文件数与展开体积,仅校验下载完整性并安全解压到本地。
|
||||
|
||||
@@ -14308,12 +14308,6 @@ const docTemplate = `{
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage_total_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -16390,12 +16384,6 @@ const docTemplate = `{
|
||||
"description": "sum(request_length)",
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reported_nodes": {
|
||||
"type": "integer"
|
||||
}
|
||||
@@ -16439,12 +16427,6 @@ const docTemplate = `{
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"node_id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -17049,12 +17031,6 @@ const docTemplate = `{
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage_total_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
@@ -14301,12 +14301,6 @@
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage_total_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -16383,12 +16377,6 @@
|
||||
"description": "sum(request_length)",
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reported_nodes": {
|
||||
"type": "integer"
|
||||
}
|
||||
@@ -16432,12 +16420,6 @@
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"node_id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -17042,12 +17024,6 @@
|
||||
"memory_used_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_rx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"network_tx_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage_total_bytes": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
@@ -714,10 +714,6 @@ definitions:
|
||||
type: integer
|
||||
memory_used_bytes:
|
||||
type: integer
|
||||
network_rx_bytes:
|
||||
type: integer
|
||||
network_tx_bytes:
|
||||
type: integer
|
||||
storage_total_bytes:
|
||||
type: integer
|
||||
storage_used_bytes:
|
||||
@@ -2096,10 +2092,6 @@ definitions:
|
||||
bytes_received:
|
||||
description: sum(request_length)
|
||||
type: integer
|
||||
network_rx_bytes:
|
||||
type: integer
|
||||
network_tx_bytes:
|
||||
type: integer
|
||||
reported_nodes:
|
||||
type: integer
|
||||
type: object
|
||||
@@ -2128,10 +2120,6 @@ definitions:
|
||||
type: integer
|
||||
memory_used_bytes:
|
||||
type: integer
|
||||
network_rx_bytes:
|
||||
type: integer
|
||||
network_tx_bytes:
|
||||
type: integer
|
||||
node_id:
|
||||
type: string
|
||||
openresty_connections:
|
||||
@@ -2527,10 +2515,6 @@ definitions:
|
||||
type: integer
|
||||
memory_used_bytes:
|
||||
type: integer
|
||||
network_rx_bytes:
|
||||
type: integer
|
||||
network_tx_bytes:
|
||||
type: integer
|
||||
storage_total_bytes:
|
||||
type: integer
|
||||
storage_used_bytes:
|
||||
|
||||
@@ -7,7 +7,22 @@ import type {
|
||||
NetworkTrendPoint,
|
||||
} from '@/lib/services/openflare';
|
||||
|
||||
import { formatBytes, formatTrendHour } from './dashboard-utils';
|
||||
import {
|
||||
formatBytes,
|
||||
formatBytesPerSecond,
|
||||
formatTrendHour,
|
||||
} from './dashboard-utils';
|
||||
|
||||
/** Backend disk points are per-hour totals; chart displays bytes/s within each hour. */
|
||||
const DISK_BUCKET_SECONDS = 3600;
|
||||
|
||||
function diskBytesToRate(bytes: number) {
|
||||
return bytes > 0 ? bytes / DISK_BUCKET_SECONDS : 0;
|
||||
}
|
||||
|
||||
function formatDiskRate(bytesPerSecond: number) {
|
||||
return formatBytesPerSecond(bytesPerSecond, 1, { zeroText: '0 B' });
|
||||
}
|
||||
|
||||
export function NetworkDiskTrendChart({
|
||||
networkPoints,
|
||||
@@ -50,54 +65,32 @@ export function NetworkDiskTrendChart({
|
||||
]}
|
||||
/>
|
||||
|
||||
<TrendChart
|
||||
labels={networkPoints.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
)}
|
||||
height={180}
|
||||
summaryScope='total'
|
||||
summaryHint='近 24 小时 · 宿主机网卡'
|
||||
yAxisValueFormatter={formatBytes}
|
||||
series={[
|
||||
{
|
||||
label: '宿主机网卡入站',
|
||||
color: '#a3e635',
|
||||
fillColor: 'rgba(163, 230, 53, 0.12)',
|
||||
variant: 'area',
|
||||
values: networkPoints.map((point) => point.network_rx_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
{
|
||||
label: '宿主机网卡出站',
|
||||
color: '#f97316',
|
||||
values: networkPoints.map((point) => point.network_tx_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<TrendChart
|
||||
labels={diskPoints.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
)}
|
||||
height={180}
|
||||
summaryScope='total'
|
||||
summaryHint='近 24 小时 · 宿主机磁盘'
|
||||
yAxisValueFormatter={formatBytes}
|
||||
summaryScope='average'
|
||||
summaryHint='近 24 小时 · 宿主机磁盘 · 平均速率'
|
||||
yAxisValueFormatter={formatDiskRate}
|
||||
series={[
|
||||
{
|
||||
label: '磁盘读',
|
||||
color: '#a78bfa',
|
||||
fillColor: 'rgba(167, 139, 250, 0.14)',
|
||||
variant: 'area',
|
||||
values: diskPoints.map((point) => point.disk_read_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
values: diskPoints.map((point) =>
|
||||
diskBytesToRate(point.disk_read_bytes),
|
||||
),
|
||||
valueFormatter: formatDiskRate,
|
||||
},
|
||||
{
|
||||
label: '磁盘写',
|
||||
color: '#fb7185',
|
||||
values: diskPoints.map((point) => point.disk_write_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
values: diskPoints.map((point) =>
|
||||
diskBytesToRate(point.disk_write_bytes),
|
||||
),
|
||||
valueFormatter: formatDiskRate,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -9,16 +9,25 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import type { DiskIOTrendPoint } from '@/lib/services/openflare';
|
||||
import { formatBytesPerSecond } from '@/lib/utils/metrics';
|
||||
|
||||
import {
|
||||
formatBytes,
|
||||
formatTrendHour,
|
||||
} from '../../components/dashboard/dashboard-utils';
|
||||
import { formatTrendHour } from '../../components/dashboard/dashboard-utils';
|
||||
|
||||
/** Backend disk points are per-hour totals; chart displays bytes/s within each hour. */
|
||||
const DISK_BUCKET_SECONDS = 3600;
|
||||
|
||||
function diskBytesToRate(bytes: number) {
|
||||
return bytes > 0 ? bytes / DISK_BUCKET_SECONDS : 0;
|
||||
}
|
||||
|
||||
function formatDiskRate(bytesPerSecond: number) {
|
||||
return formatBytesPerSecond(bytesPerSecond, 1, { zeroText: '0 B' });
|
||||
}
|
||||
|
||||
export function DiskIOTrendChart({
|
||||
points,
|
||||
title = '24 小时磁盘 IO 趋势',
|
||||
description = '观察磁盘读写变化,辅助判断日志放大、缓存抖动或磁盘压力。',
|
||||
description = '按小时展示磁盘读写速率(B/s),辅助判断日志放大、缓存抖动或磁盘压力。',
|
||||
}: {
|
||||
points: DiskIOTrendPoint[];
|
||||
title?: string;
|
||||
@@ -35,23 +44,27 @@ export function DiskIOTrendChart({
|
||||
labels={points.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
)}
|
||||
summaryScope='total'
|
||||
summaryHint='近 24 小时'
|
||||
yAxisValueFormatter={formatBytes}
|
||||
summaryScope='average'
|
||||
summaryHint='近 24 小时 · 平均速率'
|
||||
yAxisValueFormatter={formatDiskRate}
|
||||
series={[
|
||||
{
|
||||
label: '磁盘读',
|
||||
color: '#a78bfa',
|
||||
fillColor: 'rgba(167, 139, 250, 0.14)',
|
||||
variant: 'area',
|
||||
values: points.map((point) => point.disk_read_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
values: points.map((point) =>
|
||||
diskBytesToRate(point.disk_read_bytes),
|
||||
),
|
||||
valueFormatter: formatDiskRate,
|
||||
},
|
||||
{
|
||||
label: '磁盘写',
|
||||
color: '#fb7185',
|
||||
values: points.map((point) => point.disk_write_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
values: points.map((point) =>
|
||||
diskBytesToRate(point.disk_write_bytes),
|
||||
),
|
||||
valueFormatter: formatDiskRate,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
export function NetworkTrendChart({
|
||||
points,
|
||||
title = '24 小时网络趋势',
|
||||
description = '按小时展示已提供/接收数据(访问日志)与宿主机网卡;摘要为近 24 小时总量。',
|
||||
description = '按小时展示已提供/接收数据(访问日志);摘要为近 24 小时总量。',
|
||||
}: {
|
||||
points: NetworkTrendPoint[];
|
||||
title?: string;
|
||||
@@ -53,18 +53,6 @@ export function NetworkTrendChart({
|
||||
values: points.map((point) => point.bytes_provided),
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
{
|
||||
label: '宿主机网卡入站',
|
||||
color: '#a3e635',
|
||||
values: points.map((point) => point.network_rx_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
{
|
||||
label: '宿主机网卡出站',
|
||||
color: '#f97316',
|
||||
values: points.map((point) => point.network_tx_bytes),
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
@@ -717,7 +717,7 @@ export function NodeObservability({
|
||||
) : null}
|
||||
|
||||
<div className='grid gap-3 sm:grid-cols-2'>
|
||||
<div className='rounded-lg border px-3 py-3'>
|
||||
<div className='rounded-lg border px-3 py-3 sm:col-span-2'>
|
||||
<p className='text-xs text-muted-foreground uppercase tracking-wide'>
|
||||
OpenResty 连接
|
||||
</p>
|
||||
@@ -731,15 +731,6 @@ export function NodeObservability({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rounded-lg border px-3 py-3'>
|
||||
<p className='text-xs text-muted-foreground uppercase tracking-wide'>
|
||||
节点网络
|
||||
</p>
|
||||
<div className='mt-3 space-y-2 text-sm text-muted-foreground'>
|
||||
<p>入站:{formatBytes(latestMetric.network_rx_bytes)}</p>
|
||||
<p>出站:{formatBytes(latestMetric.network_tx_bytes)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-3 sm:grid-cols-2'>
|
||||
|
||||
@@ -15,7 +15,7 @@ type TrendChartSeries = {
|
||||
valueFormatter?: (value: number) => string;
|
||||
};
|
||||
|
||||
type TrendChartSummaryScope = 'last-point' | 'total';
|
||||
type TrendChartSummaryScope = 'last-point' | 'total' | 'average';
|
||||
|
||||
type TrendChartProps = {
|
||||
labels: string[];
|
||||
@@ -43,6 +43,10 @@ function resolveSummaryValue(values: number[], scope: TrendChartSummaryScope) {
|
||||
if (scope === 'total') {
|
||||
return values.reduce((sum, value) => sum + value, 0);
|
||||
}
|
||||
if (scope === 'average') {
|
||||
const sum = values.reduce((acc, value) => acc + value, 0);
|
||||
return sum / values.length;
|
||||
}
|
||||
return values[values.length - 1] ?? 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,11 +128,9 @@ function normalizeNetworkTrendPoints(
|
||||
isCompactNetworkTrendPoint(item)
|
||||
? {
|
||||
bucket_started_at: String(item[0] ?? ''),
|
||||
network_rx_bytes: Number(item[1] ?? 0),
|
||||
network_tx_bytes: Number(item[2] ?? 0),
|
||||
bytes_received: Number(item[3] ?? 0),
|
||||
bytes_provided: Number(item[4] ?? 0),
|
||||
reported_nodes: Number(item[5] ?? 0),
|
||||
bytes_received: Number(item[1] ?? 0),
|
||||
bytes_provided: Number(item[2] ?? 0),
|
||||
reported_nodes: Number(item[3] ?? 0),
|
||||
}
|
||||
: {
|
||||
...item,
|
||||
|
||||
@@ -120,8 +120,6 @@ export interface NodeMetricSnapshot {
|
||||
storage_total_bytes: number;
|
||||
disk_read_bytes: number;
|
||||
disk_write_bytes: number;
|
||||
network_rx_bytes: number;
|
||||
network_tx_bytes: number;
|
||||
openresty_connections: number;
|
||||
}
|
||||
|
||||
@@ -924,8 +922,6 @@ export interface CapacityTrendPoint {
|
||||
|
||||
export interface NetworkTrendPoint {
|
||||
bucket_started_at: string;
|
||||
network_rx_bytes: number;
|
||||
network_tx_bytes: number;
|
||||
/** L1 接收数据 sum(request_length) */
|
||||
bytes_received: number;
|
||||
/** L1 已提供数据 sum(bytes_sent) */
|
||||
@@ -985,14 +981,8 @@ export interface DashboardOverview {
|
||||
|
||||
export type CompactTrafficTrendPoint = [string, number, number, number];
|
||||
export type CompactCapacityTrendPoint = [string, number, number, number];
|
||||
export type CompactNetworkTrendPoint = [
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
/** [bucket, bytes_received, bytes_provided, reported_nodes] */
|
||||
export type CompactNetworkTrendPoint = [string, number, number, number];
|
||||
export type CompactDiskIOTrendPoint = [string, number, number, number];
|
||||
export type CompactDashboardNodeHealth = [
|
||||
number,
|
||||
|
||||
@@ -57,7 +57,7 @@ func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *protocol.NodeMe
|
||||
metric.StorageTotalBytes = storageTotal
|
||||
metric.StorageUsedBytes = storageUsed
|
||||
|
||||
metric.NetworkRxBytes, metric.NetworkTxBytes = edgeobs.ReadLinuxNetworkTotals()
|
||||
// Host NIC totals are not collected (product no longer surfaces host NIC trends).
|
||||
metric.DiskReadBytes, metric.DiskWriteBytes = edgeobs.ReadLinuxDiskTotals()
|
||||
|
||||
if stateStore == nil {
|
||||
|
||||
@@ -164,8 +164,7 @@ func persistNodeMetricSnapshot(ctx context.Context, nodeID string, snapshot *Nod
|
||||
StorageTotalBytes: snapshot.StorageTotalBytes,
|
||||
DiskReadBytes: snapshot.DiskReadBytes,
|
||||
DiskWriteBytes: snapshot.DiskWriteBytes,
|
||||
NetworkRxBytes: snapshot.NetworkRxBytes,
|
||||
NetworkTxBytes: snapshot.NetworkTxBytes,
|
||||
// NetworkRx/Tx no longer collected from agents; CH columns remain 0.
|
||||
}
|
||||
return model.InsertOpenFlareMetricSnapshot(ctx, record)
|
||||
}
|
||||
|
||||
@@ -354,12 +354,9 @@ func compressCapacityTrendPoints(points []observability.CapacityTrendPoint) [][]
|
||||
func compressNetworkTrendPoints(points []observability.NetworkTrendPoint) [][]any {
|
||||
rows := make([][]any, 0, len(points))
|
||||
for _, point := range points {
|
||||
// Compact layout (stable positions):
|
||||
// [0] bucket, [1] host_rx, [2] host_tx, [3] bytes_received, [4] bytes_provided, [5] reported_nodes
|
||||
// Compact layout: [0] bucket, [1] bytes_received, [2] bytes_provided, [3] reported_nodes
|
||||
rows = append(rows, []any{
|
||||
point.BucketStartedAt,
|
||||
point.NetworkRxBytes,
|
||||
point.NetworkTxBytes,
|
||||
point.BytesReceived,
|
||||
point.BytesProvided,
|
||||
point.ReportedNodes,
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
require.Len(t, row, 4)
|
||||
}
|
||||
for _, row := range overview.Trends.Network24h {
|
||||
require.Len(t, row, 6)
|
||||
require.Len(t, row, 4)
|
||||
}
|
||||
for _, row := range overview.Trends.DiskIO24h {
|
||||
require.Len(t, row, 4)
|
||||
|
||||
@@ -51,8 +51,6 @@ type NodeMetricSnapshotView struct {
|
||||
StorageTotalBytes int64 `json:"storage_total_bytes"`
|
||||
DiskReadBytes int64 `json:"disk_read_bytes"`
|
||||
DiskWriteBytes int64 `json:"disk_write_bytes"`
|
||||
NetworkRxBytes int64 `json:"network_rx_bytes"`
|
||||
NetworkTxBytes int64 `json:"network_tx_bytes"`
|
||||
OpenrestyConnections int64 `json:"openresty_connections"`
|
||||
}
|
||||
|
||||
@@ -95,13 +93,10 @@ type CapacityTrendPoint struct {
|
||||
ReportedNodes int `json:"reported_nodes"`
|
||||
}
|
||||
|
||||
// NetworkTrendPoint is a network trend bucket.
|
||||
// Host network_* is L3 (宿主机网卡).
|
||||
// bytes_received/provided are L1 business bytes from access logs.
|
||||
// NetworkTrendPoint is a business-byte trend bucket from access logs (L1).
|
||||
// Host NIC trends are intentionally not exposed.
|
||||
type NetworkTrendPoint struct {
|
||||
BucketStartedAt time.Time `json:"bucket_started_at"`
|
||||
NetworkRxBytes int64 `json:"network_rx_bytes"`
|
||||
NetworkTxBytes int64 `json:"network_tx_bytes"`
|
||||
BytesReceived int64 `json:"bytes_received"` // sum(request_length)
|
||||
BytesProvided int64 `json:"bytes_provided"` // sum(bytes_sent)
|
||||
ReportedNodes int `json:"reported_nodes"`
|
||||
@@ -135,11 +130,6 @@ type diskCounterState struct {
|
||||
seen bool
|
||||
}
|
||||
|
||||
type networkCounterState struct {
|
||||
rx int64
|
||||
tx int64
|
||||
seen bool
|
||||
}
|
||||
|
||||
func buildTrafficWindowSummaryFromAccessLogs(
|
||||
ctx context.Context,
|
||||
@@ -194,8 +184,6 @@ func BuildMetricSnapshotViews(
|
||||
StorageTotalBytes: snapshot.StorageTotalBytes,
|
||||
DiskReadBytes: snapshot.DiskReadBytes,
|
||||
DiskWriteBytes: snapshot.DiskWriteBytes,
|
||||
NetworkRxBytes: snapshot.NetworkRxBytes,
|
||||
NetworkTxBytes: snapshot.NetworkTxBytes,
|
||||
}
|
||||
if matched := matchEdgeHealth(snapshot.CapturedAt, edgeHealth); matched != nil {
|
||||
view.OpenrestyConnections = matched.Connections
|
||||
@@ -284,8 +272,8 @@ func buildHealthSummary(
|
||||
}
|
||||
|
||||
// BuildNodeTrends builds 24h trend series.
|
||||
// Business traffic (requests/errors/UV and provided/received bytes) comes from access logs.
|
||||
// Host capacity/disk/network come from metric snapshots (hourly when available).
|
||||
// Business traffic (requests/errors and provided/received bytes) comes from access logs.
|
||||
// Host capacity/disk come from metric snapshots (hourly when available). Host NIC is not tracked.
|
||||
func BuildNodeTrends(
|
||||
ctx context.Context,
|
||||
now time.Time,
|
||||
@@ -296,8 +284,7 @@ func BuildNodeTrends(
|
||||
|
||||
trafficTrend := BuildTrafficTrendPointsFromAccessLogs(ctx, now, nodeID, trendSince)
|
||||
capacityTrend := BuildCapacityTrendPoints(now, snapshots)
|
||||
networkTrend := BuildNetworkTrendPoints(now, snapshots)
|
||||
// Overlay L1 business bytes onto network points.
|
||||
networkTrend := emptyNetworkTrendPoints(now)
|
||||
applyAccessLogBytesToNetworkTrend(ctx, now, nodeID, trendSince, networkTrend)
|
||||
diskIOTrend := BuildDiskIOTrendPoints(now, snapshots)
|
||||
|
||||
@@ -305,8 +292,6 @@ func BuildNodeTrends(
|
||||
if metricErr == nil && len(metricHourly) > 0 {
|
||||
capacityTrend = BuildCapacityTrendPointsFromHourly(now, metricHourly)
|
||||
diskIOTrend = BuildDiskIOTrendPointsFromHourly(now, metricHourly)
|
||||
networkTrend = BuildNetworkTrendPointsFromHourly(now, metricHourly)
|
||||
applyAccessLogBytesToNetworkTrend(ctx, now, nodeID, trendSince, networkTrend)
|
||||
}
|
||||
|
||||
return NodeTrends{
|
||||
@@ -522,85 +507,12 @@ func BuildCapacityTrendPointsFromHourly(now time.Time, hourly []*model.OpenFlare
|
||||
return points
|
||||
}
|
||||
|
||||
// BuildNetworkTrendPoints builds 24h host-network trend buckets.
|
||||
// Host network counters must be process-lifetime cumulative values; this function
|
||||
// converts consecutive samples into deltas.
|
||||
func BuildNetworkTrendPoints(
|
||||
now time.Time,
|
||||
snapshots []*model.OpenFlareMetricSnapshot,
|
||||
) []NetworkTrendPoint {
|
||||
start := trendWindowStart(now)
|
||||
points := make([]NetworkTrendPoint, observabilityTrendBuckets)
|
||||
accumulators := make([]snapshotTrendAccumulator, observabilityTrendBuckets)
|
||||
for index := range points {
|
||||
points[index].BucketStartedAt = start.Add(time.Duration(index) * time.Hour)
|
||||
accumulators[index].nodes = make(map[string]struct{})
|
||||
}
|
||||
sort.Slice(snapshots, func(i int, j int) bool {
|
||||
if snapshots[i].CapturedAt.Equal(snapshots[j].CapturedAt) {
|
||||
return snapshots[i].NodeID < snapshots[j].NodeID
|
||||
}
|
||||
return snapshots[i].CapturedAt.Before(snapshots[j].CapturedAt)
|
||||
})
|
||||
previousHostByNode := make(map[string]networkCounterState, len(snapshots))
|
||||
for _, snapshot := range snapshots {
|
||||
if snapshot == nil {
|
||||
continue
|
||||
}
|
||||
nodeKey := snapshot.NodeID
|
||||
if nodeKey == "" {
|
||||
nodeKey = unknownTrendNodeKey
|
||||
}
|
||||
previous := previousHostByNode[nodeKey]
|
||||
previousHostByNode[nodeKey] = networkCounterState{
|
||||
rx: snapshot.NetworkRxBytes,
|
||||
tx: snapshot.NetworkTxBytes,
|
||||
seen: true,
|
||||
}
|
||||
if !previous.seen {
|
||||
continue
|
||||
}
|
||||
index, ok := trendBucketIndex(snapshot.CapturedAt, start)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
points[index].NetworkRxBytes += nonNegativeDelta(snapshot.NetworkRxBytes, previous.rx)
|
||||
points[index].NetworkTxBytes += nonNegativeDelta(snapshot.NetworkTxBytes, previous.tx)
|
||||
if snapshot.NodeID != "" {
|
||||
accumulators[index].nodes[snapshot.NodeID] = struct{}{}
|
||||
}
|
||||
}
|
||||
for index := range points {
|
||||
points[index].ReportedNodes = len(accumulators[index].nodes)
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
// BuildNetworkTrendPointsFromHourly builds 24h host-network trend buckets from metric hourly aggregates.
|
||||
// Business bytes (已提供/接收) are applied separately via applyAccessLogBytesToNetworkTrend.
|
||||
func BuildNetworkTrendPointsFromHourly(
|
||||
now time.Time,
|
||||
metricHourly []*model.OpenFlareMetricHourly,
|
||||
) []NetworkTrendPoint {
|
||||
func emptyNetworkTrendPoints(now time.Time) []NetworkTrendPoint {
|
||||
start := trendWindowStart(now)
|
||||
points := make([]NetworkTrendPoint, observabilityTrendBuckets)
|
||||
for index := range points {
|
||||
points[index].BucketStartedAt = start.Add(time.Duration(index) * time.Hour)
|
||||
}
|
||||
for _, row := range metricHourly {
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
index, ok := trendBucketIndex(row.Hour, start)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
points[index].NetworkRxBytes += row.NetworkRxBytes
|
||||
points[index].NetworkTxBytes += row.NetworkTxBytes
|
||||
if row.ReportedNodes > points[index].ReportedNodes {
|
||||
points[index].ReportedNodes = row.ReportedNodes
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
|
||||
@@ -113,27 +113,16 @@ func TestBuildCapacityTrendPointsFromHourlyFillsBuckets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNetworkTrendPointsUsesCounterDeltas(t *testing.T) {
|
||||
func TestEmptyNetworkTrendPointsHas24Buckets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Date(2026, 7, 10, 9, 30, 0, 0, time.UTC)
|
||||
base := now.Truncate(time.Hour)
|
||||
snapshots := []*model.OpenFlareMetricSnapshot{
|
||||
{NodeID: "n1", CapturedAt: base.Add(10 * time.Minute), NetworkRxBytes: 1000, NetworkTxBytes: 2000},
|
||||
{NodeID: "n1", CapturedAt: base.Add(20 * time.Minute), NetworkRxBytes: 1500, NetworkTxBytes: 2600},
|
||||
points := emptyNetworkTrendPoints(now)
|
||||
if len(points) != observabilityTrendBuckets {
|
||||
t.Fatalf("len(points) = %d, want %d", len(points), observabilityTrendBuckets)
|
||||
}
|
||||
|
||||
points := BuildNetworkTrendPoints(now, snapshots)
|
||||
current := points[len(points)-1]
|
||||
if current.NetworkRxBytes != 500 {
|
||||
t.Fatalf("network_rx_bytes = %d, want 500", current.NetworkRxBytes)
|
||||
}
|
||||
if current.NetworkTxBytes != 600 {
|
||||
t.Fatalf("network_tx_bytes = %d, want 600", current.NetworkTxBytes)
|
||||
}
|
||||
if current.BytesReceived != 0 || current.BytesProvided != 0 {
|
||||
t.Fatalf("business bytes should be 0 without access logs overlay, got received=%d provided=%d",
|
||||
current.BytesReceived, current.BytesProvided)
|
||||
if !points[0].BucketStartedAt.Before(points[len(points)-1].BucketStartedAt) {
|
||||
t.Fatalf("bucket order invalid: first=%v last=%v", points[0].BucketStartedAt, points[len(points)-1].BucketStartedAt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) {
|
||||
Snapshot: &agent.NodeMetricSnapshot{
|
||||
CapturedAtUnix: now.Unix(),
|
||||
CPUUsagePercent: 12.5,
|
||||
NetworkRxBytes: 1024,
|
||||
NetworkTxBytes: 2048,
|
||||
DiskReadBytes: 100,
|
||||
DiskWriteBytes: 200,
|
||||
},
|
||||
HealthEvents: []agent.NodeHealthEvent{},
|
||||
})
|
||||
|
||||
@@ -48,9 +48,8 @@ func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *service.AgentNo
|
||||
|
||||
metric.MemoryTotalBytes, metric.MemoryUsedBytes = edgeobs.ReadMemInfo()
|
||||
metric.StorageTotalBytes, metric.StorageUsedBytes = edgeobs.StatFilesystem(cfg.DataDir)
|
||||
metric.NetworkRxBytes, metric.NetworkTxBytes = edgeobs.ReadLinuxNetworkTotals()
|
||||
// Host NIC totals are not collected.
|
||||
metric.DiskReadBytes, metric.DiskWriteBytes = edgeobs.ReadLinuxDiskTotals()
|
||||
|
||||
if stateStore == nil {
|
||||
return metric
|
||||
}
|
||||
|
||||
@@ -128,10 +128,8 @@ type NodeMetricSnapshot struct {
|
||||
MemoryTotalBytes int64 `json:"memory_total_bytes"`
|
||||
StorageUsedBytes int64 `json:"storage_used_bytes"`
|
||||
StorageTotalBytes int64 `json:"storage_total_bytes"`
|
||||
DiskReadBytes int64 `json:"disk_read_bytes"`
|
||||
DiskWriteBytes int64 `json:"disk_write_bytes"`
|
||||
NetworkRxBytes int64 `json:"network_rx_bytes"`
|
||||
NetworkTxBytes int64 `json:"network_tx_bytes"`
|
||||
DiskReadBytes int64 `json:"disk_read_bytes"`
|
||||
DiskWriteBytes int64 `json:"disk_write_bytes"`
|
||||
}
|
||||
|
||||
// NodeAccessLog is an access log entry from agent (L1 business fact).
|
||||
|
||||
Reference in New Issue
Block a user