mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
699e95f12c
Result: {"status":"keep","total_issues":56,"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":5,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":55,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":47}
273 lines
6.7 KiB
Go
273 lines
6.7 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package analytics
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
|
"github.com/Rain-kl/Wavelet/internal/infra/persistence/idgen"
|
|
analyticsmodel "github.com/Rain-kl/Wavelet/internal/model/analytics"
|
|
)
|
|
|
|
const edgeHealthStatusUnknown = "unknown"
|
|
|
|
// InsertNodeMetricSnapshot writes a single metric snapshot via the batch API.
|
|
func InsertNodeMetricSnapshot(ctx context.Context, snapshot analyticsmodel.NodeMetricSnapshot) error {
|
|
if strings.TrimSpace(snapshot.NodeID) == "" {
|
|
return nil
|
|
}
|
|
return BatchInsertNodeMetricSnapshots(ctx, []analyticsmodel.NodeMetricSnapshot{snapshot})
|
|
}
|
|
|
|
// BatchInsertNodeMetricSnapshots writes metric snapshots to ClickHouse.
|
|
func BatchInsertNodeMetricSnapshots(ctx context.Context, snapshots []analyticsmodel.NodeMetricSnapshot) error {
|
|
if len(snapshots) == 0 {
|
|
return nil
|
|
}
|
|
if db.ChConn == nil {
|
|
return errors.New("clickhouse connection is not initialized")
|
|
}
|
|
|
|
batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeMetricSnapshot{}.BatchInsertSQL())
|
|
if err != nil {
|
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
for _, snapshot := range snapshots {
|
|
nodeID := strings.TrimSpace(snapshot.NodeID)
|
|
if nodeID == "" {
|
|
continue
|
|
}
|
|
id := snapshot.ID
|
|
if id == 0 {
|
|
id = idgen.NextUint64ID()
|
|
}
|
|
createdAt := snapshot.CreatedAt
|
|
if createdAt.IsZero() {
|
|
createdAt = now
|
|
}
|
|
if err := batch.Append(
|
|
id,
|
|
nodeID,
|
|
snapshot.CapturedAt.UTC(),
|
|
snapshot.CPUUsagePercent,
|
|
snapshot.MemoryUsedBytes,
|
|
snapshot.MemoryTotalBytes,
|
|
snapshot.StorageUsedBytes,
|
|
snapshot.StorageTotalBytes,
|
|
snapshot.DiskReadBytes,
|
|
snapshot.DiskWriteBytes,
|
|
snapshot.NetworkRxBytes,
|
|
snapshot.NetworkTxBytes,
|
|
createdAt.UTC(),
|
|
); err != nil {
|
|
return fmt.Errorf("append node metric snapshot to batch: %w", err)
|
|
}
|
|
}
|
|
|
|
if batch.Rows() == 0 {
|
|
return nil
|
|
}
|
|
if err := batch.Send(); err != nil {
|
|
return fmt.Errorf("send clickhouse batch: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeEdgeHealthStatus(status string) string {
|
|
status = strings.TrimSpace(status)
|
|
if status == "" {
|
|
return edgeHealthStatusUnknown
|
|
}
|
|
return status
|
|
}
|
|
|
|
// InsertNodeEdgeHealth writes a single edge health snapshot.
|
|
func InsertNodeEdgeHealth(ctx context.Context, row analyticsmodel.NodeEdgeHealth) error {
|
|
if strings.TrimSpace(row.NodeID) == "" {
|
|
return nil
|
|
}
|
|
return BatchInsertNodeEdgeHealth(ctx, []analyticsmodel.NodeEdgeHealth{row})
|
|
}
|
|
|
|
// BatchInsertNodeEdgeHealth writes L2 OpenResty health snapshots to ClickHouse.
|
|
func BatchInsertNodeEdgeHealth(ctx context.Context, rows []analyticsmodel.NodeEdgeHealth) error {
|
|
if len(rows) == 0 {
|
|
return nil
|
|
}
|
|
if db.ChConn == nil {
|
|
return errors.New("clickhouse connection is not initialized")
|
|
}
|
|
batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL())
|
|
if err != nil {
|
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
|
}
|
|
now := time.Now().UTC()
|
|
for _, row := range rows {
|
|
nodeID := strings.TrimSpace(row.NodeID)
|
|
if nodeID == "" {
|
|
continue
|
|
}
|
|
id := row.ID
|
|
if id == 0 {
|
|
id = idgen.NextUint64ID()
|
|
}
|
|
createdAt := row.CreatedAt
|
|
if createdAt.IsZero() {
|
|
createdAt = now
|
|
}
|
|
capturedAt := row.CapturedAt.UTC()
|
|
if capturedAt.IsZero() {
|
|
capturedAt = now
|
|
}
|
|
if err := batch.Append(
|
|
id,
|
|
nodeID,
|
|
capturedAt,
|
|
normalizeEdgeHealthStatus(row.Status),
|
|
row.Connections,
|
|
createdAt.UTC(),
|
|
); err != nil {
|
|
return fmt.Errorf("append node edge health to batch: %w", err)
|
|
}
|
|
}
|
|
if batch.Rows() == 0 {
|
|
return nil
|
|
}
|
|
if err := batch.Send(); err != nil {
|
|
return fmt.Errorf("send clickhouse batch: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// InsertNodeObsFrps writes a single FRPS observation via the batch API.
|
|
func InsertNodeObsFrps(ctx context.Context, obs analyticsmodel.NodeObsFrps) error {
|
|
if strings.TrimSpace(obs.NodeID) == "" {
|
|
return nil
|
|
}
|
|
return BatchInsertNodeObsFrps(ctx, []analyticsmodel.NodeObsFrps{obs})
|
|
}
|
|
|
|
// BatchInsertNodeObsFrps writes FRPS observations to ClickHouse.
|
|
func BatchInsertNodeObsFrps(ctx context.Context, observations []analyticsmodel.NodeObsFrps) error {
|
|
if len(observations) == 0 {
|
|
return nil
|
|
}
|
|
if db.ChConn == nil {
|
|
return errors.New("clickhouse connection is not initialized")
|
|
}
|
|
|
|
batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeObsFrps{}.BatchInsertSQL())
|
|
if err != nil {
|
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
for _, obs := range observations {
|
|
nodeID := strings.TrimSpace(obs.NodeID)
|
|
if nodeID == "" {
|
|
continue
|
|
}
|
|
id := obs.ID
|
|
if id == 0 {
|
|
id = idgen.NextUint64ID()
|
|
}
|
|
createdAt := obs.CreatedAt
|
|
if createdAt.IsZero() {
|
|
createdAt = now
|
|
}
|
|
capturedAt := obs.CapturedAt.UTC()
|
|
if capturedAt.IsZero() {
|
|
capturedAt = now
|
|
}
|
|
if err := batch.Append(
|
|
id,
|
|
nodeID,
|
|
capturedAt,
|
|
obs.FrpsConnections,
|
|
obs.FrpsProxyCount,
|
|
obs.FrpsClientCount,
|
|
obs.FrpsProxies,
|
|
createdAt.UTC(),
|
|
); err != nil {
|
|
return fmt.Errorf("append node frps observation to batch: %w", err)
|
|
}
|
|
}
|
|
|
|
if batch.Rows() == 0 {
|
|
return nil
|
|
}
|
|
if err := batch.Send(); err != nil {
|
|
return fmt.Errorf("send clickhouse batch: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// InsertNodeObsFrpc writes a single FRPC observation via the batch API.
|
|
func InsertNodeObsFrpc(ctx context.Context, obs analyticsmodel.NodeObsFrpc) error {
|
|
if strings.TrimSpace(obs.NodeID) == "" {
|
|
return nil
|
|
}
|
|
return BatchInsertNodeObsFrpc(ctx, []analyticsmodel.NodeObsFrpc{obs})
|
|
}
|
|
|
|
// BatchInsertNodeObsFrpc writes FRPC observations to ClickHouse.
|
|
func BatchInsertNodeObsFrpc(ctx context.Context, observations []analyticsmodel.NodeObsFrpc) error {
|
|
if len(observations) == 0 {
|
|
return nil
|
|
}
|
|
if db.ChConn == nil {
|
|
return errors.New("clickhouse connection is not initialized")
|
|
}
|
|
|
|
batch, err := db.ChConn.PrepareBatch(ctx, analyticsmodel.NodeObsFrpc{}.BatchInsertSQL())
|
|
if err != nil {
|
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
for _, obs := range observations {
|
|
nodeID := strings.TrimSpace(obs.NodeID)
|
|
if nodeID == "" {
|
|
continue
|
|
}
|
|
id := obs.ID
|
|
if id == 0 {
|
|
id = idgen.NextUint64ID()
|
|
}
|
|
createdAt := obs.CreatedAt
|
|
if createdAt.IsZero() {
|
|
createdAt = now
|
|
}
|
|
capturedAt := obs.CapturedAt.UTC()
|
|
if capturedAt.IsZero() {
|
|
capturedAt = now
|
|
}
|
|
if err := batch.Append(
|
|
id,
|
|
nodeID,
|
|
capturedAt,
|
|
obs.TunnelStatus,
|
|
obs.ConnectedRelaysCount,
|
|
createdAt.UTC(),
|
|
); err != nil {
|
|
return fmt.Errorf("append node frpc observation to batch: %w", err)
|
|
}
|
|
}
|
|
|
|
if batch.Rows() == 0 {
|
|
return nil
|
|
}
|
|
if err := batch.Send(); err != nil {
|
|
return fmt.Errorf("send clickhouse batch: %w", err)
|
|
}
|
|
return nil
|
|
}
|