mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
aa4faddade
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"vitest_failed":0,"vitest_total":116,"measure_s":81}
124 lines
4.1 KiB
Go
124 lines
4.1 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package observability collects relay node profile data for heartbeat reporting.
|
|
package observability
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
edgeobs "github.com/Rain-kl/Wavelet/internal/apps/edge/observability"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/relay/config"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/relay/frps"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/relay/state"
|
|
service "github.com/Rain-kl/Wavelet/pkg/protocol"
|
|
)
|
|
|
|
// BuildProfile collects the node's system profile and returns it only when the
|
|
// fingerprint has changed since the last heartbeat, avoiding redundant uploads.
|
|
func BuildProfile(cfg *config.Config, stateStore *state.Store) *service.AgentNodeSystemProfile {
|
|
profile := collectProfile(cfg)
|
|
if profile == nil || stateStore == nil {
|
|
return profile
|
|
}
|
|
fingerprint := fingerprintProfile(profile)
|
|
snapshot, err := stateStore.Load()
|
|
if err != nil {
|
|
return profile
|
|
}
|
|
if snapshot.LastProfileFingerprint == fingerprint {
|
|
return nil
|
|
}
|
|
snapshot.LastProfileFingerprint = fingerprint
|
|
if err = stateStore.Save(snapshot); err != nil {
|
|
return profile
|
|
}
|
|
return profile
|
|
}
|
|
|
|
// BuildSnapshot captures a point-in-time metric snapshot including memory,
|
|
// disk, network I/O, and CPU usage computed from a delta against the last
|
|
// persisted CPU stat.
|
|
func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *service.AgentNodeMetricSnapshot {
|
|
now := time.Now().UTC()
|
|
metric := &service.AgentNodeMetricSnapshot{CapturedAtUnix: now.Unix()}
|
|
|
|
metric.MemoryTotalBytes, metric.MemoryUsedBytes = edgeobs.ReadMemInfo()
|
|
metric.StorageTotalBytes, metric.StorageUsedBytes = edgeobs.StatFilesystem(cfg.DataDir)
|
|
// Host NIC totals are not collected.
|
|
metric.DiskReadBytes, metric.DiskWriteBytes = edgeobs.ReadLinuxDiskTotals()
|
|
if stateStore == nil {
|
|
return metric
|
|
}
|
|
totalCPU, idleCPU := edgeobs.ReadLinuxCPUStat()
|
|
snapshot, err := stateStore.Load()
|
|
if err != nil {
|
|
return metric
|
|
}
|
|
if snapshot.LastCPUStatTotal > 0 && totalCPU > snapshot.LastCPUStatTotal && idleCPU >= snapshot.LastCPUStatIdle {
|
|
deltaTotal := totalCPU - snapshot.LastCPUStatTotal
|
|
deltaIdle := idleCPU - snapshot.LastCPUStatIdle
|
|
if deltaTotal > 0 && deltaIdle <= deltaTotal {
|
|
metric.CPUUsagePercent = float64(deltaTotal-deltaIdle) / float64(deltaTotal) * 100
|
|
}
|
|
}
|
|
snapshot.LastCPUStatTotal = totalCPU
|
|
snapshot.LastCPUStatIdle = idleCPU
|
|
snapshot.LastMetricAtUnix = now.Unix()
|
|
_ = stateStore.Save(snapshot)
|
|
return metric
|
|
}
|
|
|
|
// BuildHealthEvents converts a RuntimeStatus into a list of health events.
|
|
// An empty slice is returned when frps is healthy.
|
|
func BuildHealthEvents(status frps.RuntimeStatus) []service.AgentNodeHealthEvent {
|
|
if strings.TrimSpace(status.Status) == "healthy" {
|
|
return []service.AgentNodeHealthEvent{}
|
|
}
|
|
message := strings.TrimSpace(status.LastError)
|
|
if message == "" {
|
|
message = "frps runtime is not healthy"
|
|
}
|
|
return []service.AgentNodeHealthEvent{{
|
|
EventType: "frps_unhealthy",
|
|
Severity: "critical",
|
|
Message: message,
|
|
TriggeredAtUnix: time.Now().UTC().Unix(),
|
|
}}
|
|
}
|
|
|
|
func collectProfile(cfg *config.Config) *service.AgentNodeSystemProfile {
|
|
hostname, _ := os.Hostname()
|
|
osName, osVersion := edgeobs.ReadLinuxOSRelease()
|
|
totalMemory, _ := edgeobs.ReadMemInfo()
|
|
totalDisk, _ := edgeobs.StatFilesystem(cfg.DataDir)
|
|
return &service.AgentNodeSystemProfile{
|
|
Hostname: strings.TrimSpace(hostname),
|
|
OSName: osName,
|
|
OSVersion: osVersion,
|
|
KernelVersion: edgeobs.ReadFirstLine("/proc/sys/kernel/osrelease"),
|
|
Architecture: runtime.GOARCH,
|
|
CPUModel: edgeobs.ReadLinuxCPUModel(),
|
|
CPUCores: runtime.NumCPU(),
|
|
TotalMemoryBytes: totalMemory,
|
|
TotalDiskBytes: totalDisk,
|
|
UptimeSeconds: edgeobs.ReadLinuxUptimeSeconds(),
|
|
ReportedAtUnix: time.Now().UTC().Unix(),
|
|
}
|
|
}
|
|
|
|
func fingerprintProfile(profile *service.AgentNodeSystemProfile) string {
|
|
raw, err := json.Marshal(profile)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|