0d84b07f68
CI / docker (push) Successful in 1m58s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
package metrics
|
|
|
|
import (
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/shirou/gopsutil/v3/cpu"
|
|
"github.com/shirou/gopsutil/v3/load"
|
|
"github.com/shirou/gopsutil/v3/mem"
|
|
"github.com/wormhole/wormhole/internal/bridge"
|
|
"github.com/wormhole/wormhole/internal/cache"
|
|
"github.com/wormhole/wormhole/internal/store"
|
|
)
|
|
|
|
type Collector struct {
|
|
store *store.Store
|
|
cache *cache.Manager
|
|
bridge *bridge.Bridge
|
|
stop chan struct{}
|
|
}
|
|
|
|
func NewCollector(st *store.Store, c *cache.Manager, b *bridge.Bridge) *Collector {
|
|
return &Collector{
|
|
store: st,
|
|
cache: c,
|
|
bridge: b,
|
|
stop: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (c *Collector) Start(flushInterval time.Duration) {
|
|
go func() {
|
|
ticker := time.NewTicker(flushInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
c.flushFlow()
|
|
case <-c.stop:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (c *Collector) Stop() {
|
|
close(c.stop)
|
|
}
|
|
|
|
func (c *Collector) flushFlow() {
|
|
deltas := c.cache.DrainFlow()
|
|
for key, delta := range deltas {
|
|
parts := strings.SplitN(key, ":", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
resourceType := parts[0]
|
|
resourceID, err := strconv.ParseUint(parts[1], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if err := c.store.UpsertFlowStat(resourceType, uint(resourceID), delta.Inlet, delta.Export); err != nil {
|
|
log.Printf("[metrics] flush flow stat: %v", err)
|
|
}
|
|
switch resourceType {
|
|
case "client":
|
|
_ = c.store.AddClientFlow(uint(resourceID), delta.Inlet, delta.Export)
|
|
case "tunnel":
|
|
_ = c.store.AddTunnelFlow(uint(resourceID), delta.Inlet, delta.Export)
|
|
case "host":
|
|
_ = c.store.AddHostFlow(uint(resourceID), delta.Inlet, delta.Export)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Collector) RecordRequestIP(resourceType string, resourceID uint, visitorIP, directIP string) {
|
|
if err := c.store.RecordRequestIP(resourceType, resourceID, visitorIP, directIP); err != nil {
|
|
log.Printf("[metrics] record request ip: %v", err)
|
|
}
|
|
}
|
|
|
|
func (c *Collector) AddFlow(resourceType string, resourceID uint, clientID uint, inlet, export int64) {
|
|
c.cache.AddFlow(resourceType, resourceID, inlet, export)
|
|
if clientID > 0 {
|
|
c.cache.AddFlow("client", clientID, inlet, export)
|
|
}
|
|
}
|
|
|
|
func (c *Collector) Dashboard() (map[string]interface{}, error) {
|
|
stats, err := c.store.DashboardStats()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["client_online"] = c.bridge.OnlineCount()
|
|
|
|
cpuPercent, _ := cpu.Percent(0, false)
|
|
memInfo, _ := mem.VirtualMemory()
|
|
loadInfo, _ := load.Avg()
|
|
|
|
var cpuVal float64
|
|
if len(cpuPercent) > 0 {
|
|
cpuVal = cpuPercent[0]
|
|
}
|
|
|
|
topClients, _ := c.store.TopClientsByFlow(10)
|
|
topTunnels, _ := c.store.TopTunnelsByFlow(10)
|
|
topHosts, _ := c.store.TopHostsByFlow(10)
|
|
topIPs, _ := c.store.TopRequestIPs(10)
|
|
|
|
stats["cpu_percent"] = cpuVal
|
|
stats["mem_percent"] = memInfo.UsedPercent
|
|
stats["load1"] = loadInfo.Load1
|
|
stats["top_clients"] = topClients
|
|
stats["top_tunnels"] = topTunnels
|
|
stats["top_hosts"] = topHosts
|
|
stats["top_ips"] = topIPs
|
|
return stats, nil
|
|
}
|