mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +08:00
feat(obs): 访问日志 SSOT 与 edge_health,去掉协议兼容层
Agent 仅上报 host_metrics/edge_health/access_logs;业务流量与 UV 由 Server 侧访问日志聚合。新增 of_node_edge_health 与 of_access_log_hourly, 删除 request_reports/openresty 吞吐路径;API 不再暴露 traffic_reports 与 openresty_rx|tx。心跳/离线默认阈值与回填迁移一并入库。
This commit is contained in:
@@ -31,8 +31,8 @@ func computeNodeStatus(node *model.OpenFlareNode) string {
|
||||
if node.LastSeenAt == nil || node.LastSeenAt.IsZero() {
|
||||
return nodeStatusPending
|
||||
}
|
||||
// 使用默认阈值 2 分钟
|
||||
threshold := 2 * time.Minute
|
||||
// 默认离线阈值 60 秒(与 node_offline_threshold 默认一致)
|
||||
threshold := 60 * time.Second
|
||||
if time.Since(*node.LastSeenAt) > threshold {
|
||||
return nodeStatusOffline
|
||||
}
|
||||
|
||||
@@ -122,19 +122,10 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
latestTrafficRows, err := model.ListOpenFlareLatestRequestReportsSince(ctx, "", since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Bounded raw windows remain for distributions and trend fallbacks; trends prefer hourly rollups.
|
||||
snapshots, err := model.ListOpenFlareMetricSnapshotsSince(ctx, "", since, dashboardOverviewSnapshotLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reports, err := model.ListOpenFlareRequestReportsSince(ctx, "", since, dashboardOverviewSnapshotLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessLogRegions, err := model.ListOpenFlareAccessLogRegionCounts(ctx, "", since, dashboardDistributionLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -143,21 +134,48 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openrestySnapshots, err := model.ListOpenFlareNodeObservationOpenresty(ctx, "", since, dashboardOverviewSnapshotLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// L1 business: trends + distributions + totals from access logs only.
|
||||
view := &OverviewView{
|
||||
GeneratedAt: now,
|
||||
Nodes: make([]NodeHealth, 0, len(nodes)),
|
||||
Distributions: observability.BuildTrafficDistributions(reports, accessLogRegions, dashboardDistributionLimit),
|
||||
Trends: observability.BuildNodeTrends(ctx, now, "", snapshots, openrestySnapshots, reports),
|
||||
GeneratedAt: now,
|
||||
Nodes: make([]NodeHealth, 0, len(nodes)),
|
||||
Distributions: observability.BuildTrafficDistributionsFromAccessLogs(
|
||||
ctx, since, now, dashboardDistributionLimit, accessLogRegions,
|
||||
),
|
||||
Trends: observability.BuildNodeTrends(ctx, now, "", snapshots),
|
||||
}
|
||||
|
||||
// Global traffic summary uses true window uniqExact for UV (not sum of hourly uniques).
|
||||
if summary, sumErr := model.TrafficSummaryOpenFlareAccessLogs(ctx, model.OpenFlareAccessLogQuery{
|
||||
Since: since,
|
||||
Until: now,
|
||||
}); sumErr == nil {
|
||||
view.Traffic.RequestCount = summary.RequestCount
|
||||
view.Traffic.ErrorCount = summary.ErrorCount
|
||||
view.Traffic.UniqueVisitors = summary.UniqueIPCount
|
||||
view.Traffic.ReportedNodes = int(summary.NodeCount)
|
||||
if summary.RequestCount > 0 {
|
||||
// Average QPS over the 24h window.
|
||||
view.Traffic.EstimatedQPS = float64(summary.RequestCount) / (24 * 3600)
|
||||
}
|
||||
} else {
|
||||
// Fallback: sum hourly request/error buckets only (UV left from summary path).
|
||||
applyTrafficTotalsFromTrend(&view.Traffic, view.Trends.Traffic24h)
|
||||
}
|
||||
|
||||
nodeTraffic := map[string]model.OpenFlareAccessLogNodeAggregate{}
|
||||
if aggregates, aggErr := model.NodeAggregatesOpenFlareAccessLogs(ctx, model.OpenFlareAccessLogQuery{
|
||||
Since: since,
|
||||
Until: now,
|
||||
}); aggErr == nil {
|
||||
for _, row := range aggregates {
|
||||
nodeTraffic[row.NodeID] = row
|
||||
}
|
||||
}
|
||||
|
||||
var cpuNodeCount int
|
||||
var memoryNodeCount int
|
||||
latestSnapshots := observability.LatestMetricSnapshotsByNode(latestSnapshotRows)
|
||||
latestTrafficReports := observability.LatestTrafficReportsByNode(latestTrafficRows)
|
||||
activeEventsByNode := observability.ActiveHealthEventsByNode(activeEvents)
|
||||
|
||||
for _, node := range nodes {
|
||||
@@ -175,7 +193,6 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) {
|
||||
}
|
||||
|
||||
latestSnapshot := latestSnapshots[node.NodeID]
|
||||
latestTraffic := latestTrafficReports[node.NodeID]
|
||||
nodeActiveEvents := activeEventsByNode[node.NodeID]
|
||||
|
||||
nodeHealth := NodeHealth{
|
||||
@@ -193,14 +210,15 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) {
|
||||
}
|
||||
|
||||
cpuNodeCount, memoryNodeCount = applyNodeSnapshotMetrics(&nodeHealth, latestSnapshot, view, cpuNodeCount, memoryNodeCount)
|
||||
applyNodeTrafficMetrics(&nodeHealth, latestTraffic)
|
||||
if agg, ok := nodeTraffic[node.NodeID]; ok {
|
||||
nodeHealth.RequestCount = agg.RequestCount
|
||||
nodeHealth.ErrorCount = agg.ErrorCount
|
||||
nodeHealth.UniqueVisitorCount = agg.UniqueIPCount
|
||||
}
|
||||
|
||||
view.Nodes = append(view.Nodes, nodeHealth)
|
||||
}
|
||||
|
||||
applyTrafficTotalsFromTrend(&view.Traffic, view.Trends.Traffic24h)
|
||||
applyTrafficRuntimeMetrics(&view.Traffic, latestTrafficReports)
|
||||
|
||||
view.Summary.TotalNodes = len(nodes)
|
||||
if cpuNodeCount > 0 {
|
||||
view.Capacity.AverageCPUUsagePercent /= float64(cpuNodeCount)
|
||||
@@ -246,43 +264,19 @@ func applyNodeSnapshotMetrics(nodeHealth *NodeHealth, snapshot *model.OpenFlareM
|
||||
return cpuNodeCount, memoryNodeCount
|
||||
}
|
||||
|
||||
func applyNodeTrafficMetrics(nodeHealth *NodeHealth, traffic *model.OpenFlareRequestReport) {
|
||||
if traffic == nil {
|
||||
return
|
||||
}
|
||||
nodeHealth.RequestCount = traffic.RequestCount
|
||||
nodeHealth.ErrorCount = traffic.ErrorCount
|
||||
nodeHealth.UniqueVisitorCount = traffic.UniqueVisitorCount
|
||||
}
|
||||
|
||||
func applyTrafficTotalsFromTrend(traffic *Traffic, points []observability.TrafficTrendPoint) {
|
||||
if traffic == nil {
|
||||
return
|
||||
}
|
||||
traffic.RequestCount = 0
|
||||
traffic.ErrorCount = 0
|
||||
// Do not sum hourly unique visitors — that overcounts. UV must come from TrafficSummary.
|
||||
for _, point := range points {
|
||||
traffic.RequestCount += point.RequestCount
|
||||
traffic.ErrorCount += point.ErrorCount
|
||||
}
|
||||
}
|
||||
|
||||
func applyTrafficRuntimeMetrics(traffic *Traffic, latestReports map[string]*model.OpenFlareRequestReport) {
|
||||
if traffic == nil {
|
||||
return
|
||||
}
|
||||
traffic.UniqueVisitors = 0
|
||||
traffic.EstimatedQPS = 0
|
||||
traffic.ReportedNodes = 0
|
||||
for _, report := range latestReports {
|
||||
if report == nil {
|
||||
continue
|
||||
}
|
||||
traffic.UniqueVisitors += report.UniqueVisitorCount
|
||||
traffic.ReportedNodes++
|
||||
if duration := report.WindowEndedAt.Sub(report.WindowStartedAt).Seconds(); duration > 0 {
|
||||
traffic.EstimatedQPS += float64(report.RequestCount) / duration
|
||||
}
|
||||
if traffic.RequestCount > 0 && traffic.ReportedNodes == 0 {
|
||||
traffic.ReportedNodes = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,12 +354,14 @@ 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
|
||||
rows = append(rows, []any{
|
||||
point.BucketStartedAt,
|
||||
point.NetworkRxBytes,
|
||||
point.NetworkTxBytes,
|
||||
point.OpenrestyRxBytes,
|
||||
point.OpenrestyTxBytes,
|
||||
point.BytesReceived,
|
||||
point.BytesProvided,
|
||||
point.ReportedNodes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC()
|
||||
lastSeen := now.Add(-time.Minute)
|
||||
lastSeen := now.Add(-15 * time.Second) // within default 60s offline threshold
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-dashboard-1",
|
||||
@@ -75,14 +75,33 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
StorageUsedBytes: 2,
|
||||
StorageTotalBytes: 10,
|
||||
}))
|
||||
require.NoError(t, model.InsertOpenFlareRequestReport(ctx, &model.OpenFlareRequestReport{
|
||||
NodeID: "node-dashboard-1",
|
||||
WindowStartedAt: now.Add(-2 * time.Minute),
|
||||
WindowEndedAt: now.Add(-time.Minute),
|
||||
RequestCount: 12,
|
||||
ErrorCount: 1,
|
||||
UniqueVisitorCount: 4,
|
||||
}))
|
||||
// Business traffic from access logs (L1 authority): 12 requests, 1 server error, 4 unique IPs.
|
||||
logs := make([]*model.OpenFlareAccessLog, 0, 12)
|
||||
for i := 0; i < 11; i++ {
|
||||
logs = append(logs, &model.OpenFlareAccessLog{
|
||||
NodeID: "node-dashboard-1",
|
||||
LoggedAt: now.Add(-time.Minute),
|
||||
RemoteAddr: "10.0.0." + string(rune('1'+i%4)), // rough; fixed below
|
||||
Host: "app.example.com",
|
||||
Path: "/",
|
||||
StatusCode: 200,
|
||||
BytesSent: 100,
|
||||
})
|
||||
}
|
||||
ips := []string{"10.0.0.10", "10.0.0.11", "10.0.0.12", "10.0.0.13"}
|
||||
for i := 0; i < 11; i++ {
|
||||
logs[i].RemoteAddr = ips[i%4]
|
||||
}
|
||||
logs = append(logs, &model.OpenFlareAccessLog{
|
||||
NodeID: "node-dashboard-1",
|
||||
LoggedAt: now.Add(-time.Minute),
|
||||
RemoteAddr: ips[0],
|
||||
Host: "app.example.com",
|
||||
Path: "/err",
|
||||
StatusCode: 502,
|
||||
BytesSent: 10,
|
||||
})
|
||||
require.NoError(t, model.InsertOpenFlareAccessLogsBatch(ctx, logs))
|
||||
|
||||
overview, err := GetOverview(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -98,8 +117,12 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
assert.Equal(t, int64(12), overview.Traffic.RequestCount)
|
||||
assert.Equal(t, int64(4), overview.Traffic.UniqueVisitors)
|
||||
assert.Equal(t, int64(1), overview.Traffic.ErrorCount)
|
||||
assert.InDelta(t, 0.2, overview.Traffic.EstimatedQPS, 0.0001)
|
||||
// QPS over 24h window
|
||||
assert.InDelta(t, 12.0/(24*3600.0), overview.Traffic.EstimatedQPS, 0.0001)
|
||||
assert.Equal(t, 1, overview.Traffic.ReportedNodes)
|
||||
// Node-level traffic from access log aggregates
|
||||
onlineNodeCheck := overview.Nodes
|
||||
require.NotEmpty(t, onlineNodeCheck)
|
||||
|
||||
assert.Equal(t, 55.0, overview.Capacity.AverageCPUUsagePercent)
|
||||
assert.Equal(t, 50.0, overview.Capacity.AverageMemoryUsagePercent)
|
||||
@@ -110,8 +133,9 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
require.NotNil(t, overview.Distributions.StatusCodes)
|
||||
require.NotNil(t, overview.Distributions.TopDomains)
|
||||
require.NotNil(t, overview.Distributions.SourceCountries)
|
||||
assert.Empty(t, overview.Distributions.StatusCodes)
|
||||
assert.Empty(t, overview.Distributions.TopDomains)
|
||||
// Status/top domains come from access logs.
|
||||
assert.NotEmpty(t, overview.Distributions.StatusCodes)
|
||||
assert.NotEmpty(t, overview.Distributions.TopDomains)
|
||||
assert.Empty(t, overview.Distributions.SourceCountries)
|
||||
|
||||
require.Len(t, overview.Trends.Traffic24h, 24)
|
||||
@@ -147,11 +171,11 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
assert.Equal(t, "online", onlineNode[6])
|
||||
assert.Equal(t, "healthy", onlineNode[7])
|
||||
// Latest-per-node health fields (indexes match compressDashboardNodes).
|
||||
assert.Equal(t, 55.0, onlineNode[11]) // cpu_usage_percent from latest snapshot
|
||||
assert.Equal(t, 50.0, onlineNode[12]) // memory_usage_percent
|
||||
assert.Equal(t, int64(12), onlineNode[14])
|
||||
assert.Equal(t, int64(1), onlineNode[15])
|
||||
assert.Equal(t, int64(4), onlineNode[16])
|
||||
assert.Equal(t, 55.0, onlineNode[11]) // cpu_usage_percent from latest snapshot
|
||||
assert.Equal(t, 50.0, onlineNode[12]) // memory_usage_percent
|
||||
assert.Equal(t, int64(12), onlineNode[14]) // request_count from access logs
|
||||
assert.Equal(t, int64(1), onlineNode[15]) // error_count
|
||||
assert.Equal(t, int64(4), onlineNode[16]) // unique visitors
|
||||
|
||||
pendingNode := nodeByID["node-dashboard-2"]
|
||||
require.NotNil(t, pendingNode)
|
||||
@@ -161,5 +185,4 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 55.0, overview.Capacity.AverageCPUUsagePercent)
|
||||
assert.Equal(t, 1, overview.Traffic.ReportedNodes)
|
||||
assert.Equal(t, int64(4), overview.Traffic.UniqueVisitors)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user