mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
deb232d840
- Align agent, relay, and flared to dynamically resolve IP during heartbeat using the nodeip package (when not manually configured). - Update GeoIP outbound IP strategy to prefer IPv4 HTTP client lookup using tcp4 dialer and fall back to dual-stack tcp. - Optimize agent profile fingerprinting to exclude dynamic UptimeSeconds and ReportedAtUnix fields, preventing redundant updates. - Refactor unit tests to prevent outbound network queries during tests.
161 lines
5.0 KiB
Go
161 lines
5.0 KiB
Go
// Package observability provides system and service level observability data collection for the agent.
|
|
package observability
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/agent/config"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/agent/protocol"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/agent/state"
|
|
edgeobs "github.com/Rain-kl/Wavelet/internal/apps/edge/observability"
|
|
)
|
|
|
|
const nodeHealthEventInitialCapacity = 2
|
|
|
|
// BuildProfile collects the system profile and returns it only if the fingerprint has changed.
|
|
func BuildProfile(cfg *config.Config, stateStore *state.Store) *protocol.NodeSystemProfile {
|
|
profile := collectProfile(cfg)
|
|
if profile == nil {
|
|
return nil
|
|
}
|
|
fingerprint := fingerprintProfile(profile)
|
|
if stateStore == nil {
|
|
return 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 current system metrics and returns a metric snapshot.
|
|
func BuildSnapshot(cfg *config.Config, stateStore *state.Store) *protocol.NodeMetricSnapshot {
|
|
now := time.Now().UTC()
|
|
metric := &protocol.NodeMetricSnapshot{
|
|
CapturedAtUnix: now.Unix(),
|
|
}
|
|
|
|
memTotal, memUsed := edgeobs.ReadMemInfo()
|
|
metric.MemoryTotalBytes = memTotal
|
|
metric.MemoryUsedBytes = memUsed
|
|
|
|
storageTotal, storageUsed := edgeobs.StatFilesystem(cfg.DataDir)
|
|
metric.StorageTotalBytes = storageTotal
|
|
metric.StorageUsedBytes = storageUsed
|
|
|
|
metric.NetworkRxBytes, metric.NetworkTxBytes = edgeobs.ReadLinuxNetworkTotals()
|
|
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
|
|
}
|
|
|
|
// BuildOpenrestyObservation builds the OpenResty observation protocol model from the managed metrics.
|
|
func BuildOpenrestyObservation(managed *ManagedOpenRestyMetrics) *protocol.NodeOpenrestyObservation {
|
|
if managed == nil {
|
|
return nil
|
|
}
|
|
return &protocol.NodeOpenrestyObservation{
|
|
CapturedAtUnix: time.Now().UTC().Unix(),
|
|
OpenrestyRxBytes: managed.OpenrestyRxBytes,
|
|
OpenrestyTxBytes: managed.OpenrestyTxBytes,
|
|
OpenrestyConnections: managed.OpenrestyConnections,
|
|
}
|
|
}
|
|
|
|
// BuildHealthEvents converts system snapshot health state into a list of health events.
|
|
func BuildHealthEvents(snapshot *state.Snapshot) []protocol.NodeHealthEvent {
|
|
if snapshot == nil {
|
|
return []protocol.NodeHealthEvent{}
|
|
}
|
|
events := make([]protocol.NodeHealthEvent, 0, nodeHealthEventInitialCapacity)
|
|
nowUnix := time.Now().UTC().Unix()
|
|
if strings.TrimSpace(snapshot.OpenrestyStatus) == protocol.OpenrestyStatusUnhealthy {
|
|
events = append(events, protocol.NodeHealthEvent{
|
|
EventType: "openresty_unhealthy",
|
|
Severity: "critical",
|
|
Message: strings.TrimSpace(snapshot.OpenrestyMessage),
|
|
TriggeredAtUnix: nowUnix,
|
|
})
|
|
}
|
|
if strings.TrimSpace(snapshot.LastError) != "" {
|
|
events = append(events, protocol.NodeHealthEvent{
|
|
EventType: "sync_error",
|
|
Severity: "warning",
|
|
Message: strings.TrimSpace(snapshot.LastError),
|
|
TriggeredAtUnix: nowUnix,
|
|
})
|
|
}
|
|
return events
|
|
}
|
|
|
|
func collectProfile(cfg *config.Config) *protocol.NodeSystemProfile {
|
|
hostname, _ := os.Hostname()
|
|
osName, osVersion := edgeobs.ReadLinuxOSRelease()
|
|
kernelVersion := edgeobs.ReadFirstLine("/proc/sys/kernel/osrelease")
|
|
cpuModel := edgeobs.ReadLinuxCPUModel()
|
|
totalMemory, _ := edgeobs.ReadMemInfo()
|
|
totalDisk, _ := edgeobs.StatFilesystem(cfg.DataDir)
|
|
uptimeSeconds := edgeobs.ReadLinuxUptimeSeconds()
|
|
|
|
return &protocol.NodeSystemProfile{
|
|
Hostname: strings.TrimSpace(hostname),
|
|
OSName: osName,
|
|
OSVersion: osVersion,
|
|
KernelVersion: kernelVersion,
|
|
Architecture: runtime.GOARCH,
|
|
CPUModel: cpuModel,
|
|
CPUCores: runtime.NumCPU(),
|
|
TotalMemoryBytes: totalMemory,
|
|
TotalDiskBytes: totalDisk,
|
|
UptimeSeconds: uptimeSeconds,
|
|
ReportedAtUnix: time.Now().UTC().Unix(),
|
|
}
|
|
}
|
|
|
|
func fingerprintProfile(profile *protocol.NodeSystemProfile) string {
|
|
cloned := *profile
|
|
cloned.UptimeSeconds = 0
|
|
cloned.ReportedAtUnix = 0
|
|
raw, err := json.Marshal(&cloned)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|