Files
kefu_cloud/server/internal/metrics/metrics.go
T
yml2213 9251815695 接入运维真实 metrics:负载、请求错误率与服务探测
- 新增 GET /admin/ops/metrics 与进程内请求统计中间件
- 运维页展示 CPU/内存/磁盘、24h 曲线与 WS/DB/存储状态
- 去掉未使用的消息队列占位项
2026-07-15 15:01:18 +08:00

141 lines
2.8 KiB
Go

package metrics
import (
"sync"
"sync/atomic"
"time"
"github.com/gin-gonic/gin"
)
// Collector 进程内请求统计:近 24 小时按整点小时桶累加。
type Collector struct {
mu sync.Mutex
byHour map[int64]*hourBucket
// 自进程启动以来的累计
totalReq atomic.Int64
totalErr atomic.Int64 // HTTP status >= 400
total5xx atomic.Int64
inFlight atomic.Int64
startedAt time.Time
}
type hourBucket struct {
requests int64
errors int64
status5 int64
}
// Default 全局采集器
var Default = NewCollector()
func NewCollector() *Collector {
return &Collector{
byHour: make(map[int64]*hourBucket),
startedAt: time.Now(),
}
}
func hourKey(t time.Time) int64 {
return t.Unix() / 3600
}
func (c *Collector) Record(status int) {
c.totalReq.Add(1)
isErr := status >= 400
is5 := status >= 500
if isErr {
c.totalErr.Add(1)
}
if is5 {
c.total5xx.Add(1)
}
h := hourKey(time.Now())
c.mu.Lock()
b := c.byHour[h]
if b == nil {
b = &hourBucket{}
c.byHour[h] = b
c.pruneLocked(h)
}
b.requests++
if isErr {
b.errors++
}
if is5 {
b.status5++
}
c.mu.Unlock()
}
func (c *Collector) pruneLocked(nowHour int64) {
cutoff := nowHour - 25
for k := range c.byHour {
if k < cutoff {
delete(c.byHour, k)
}
}
}
// Snapshot24h 返回近 24 个整点小时(含当前小时)的请求量与错误率(0–100)。
// 下标 0 为 23 小时前,23 为当前小时;hours 为当地小时 0–23。
func (c *Collector) Snapshot24h() (requests []int64, errorRates []float64, hours []int, totalReq, totalErr int64) {
now := time.Now()
nowH := hourKey(now)
loc := now.Location()
requests = make([]int64, 24)
errorRates = make([]float64, 24)
hours = make([]int, 24)
c.mu.Lock()
defer c.mu.Unlock()
for i := 0; i < 24; i++ {
hKey := nowH - int64(23-i)
t := time.Unix(hKey*3600, 0).In(loc)
hours[i] = t.Hour()
b := c.byHour[hKey]
var r, e int64
if b != nil {
r, e = b.requests, b.errors
}
requests[i] = r
if r > 0 {
errorRates[i] = float64(e) / float64(r) * 100
} else {
errorRates[i] = 0
}
totalReq += r
totalErr += e
}
return
}
func (c *Collector) Totals() (req, err4, err5 int64, startedAt time.Time) {
return c.totalReq.Load(), c.totalErr.Load(), c.total5xx.Load(), c.startedAt
}
func (c *Collector) InFlight() int64 {
return c.inFlight.Load()
}
// Middleware 记录每个 HTTP 请求的状态码(跳过 WebSocket 升级失败后的统计由 status 决定)。
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 健康检查可不计入业务错误曲线,但仍记请求量更直观;这里全部计入
Default.inFlight.Add(1)
c.Next()
Default.inFlight.Add(-1)
// WebSocket 升级成功后 status 可能是 101
status := c.Writer.Status()
if status == 0 {
status = 200
}
Default.Record(status)
}
}