Files
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

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
}