128 lines
3.1 KiB
Go
128 lines
3.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// 简易 QPS / QPM 统计:使用滑动窗口环形数组按秒/按分钟聚合
|
|
// secondBuckets: 最近 60 秒每秒的请求计数
|
|
// minuteBuckets: 最近 60 分钟每分钟的请求计数
|
|
|
|
var (
|
|
secondBuckets [60]atomic.Int64
|
|
minuteBuckets [60]atomic.Int64
|
|
lastSecond int64
|
|
lastMinute int64
|
|
// 总请求数 (复用可选)
|
|
totalRequests atomic.Int64
|
|
)
|
|
|
|
func init() {
|
|
now := time.Now()
|
|
lastSecond = now.Unix()
|
|
lastMinute = now.Unix() / 60
|
|
}
|
|
|
|
// AddRequest 在收到一个请求时调用,通常在请求完成后计数
|
|
func AddRequest() {
|
|
now := time.Now()
|
|
sec := now.Unix()
|
|
min := sec / 60
|
|
|
|
// 处理秒级 bucket
|
|
oldSec := atomic.LoadInt64(&lastSecond)
|
|
if sec != oldSec {
|
|
// 跨秒:清理可能跨越多个秒的间隙
|
|
if atomic.CompareAndSwapInt64(&lastSecond, oldSec, sec) {
|
|
steps := int(sec - oldSec)
|
|
if steps > 60 { steps = 60 }
|
|
for i := 1; i <= steps; i++ {
|
|
idx := int((oldSec+int64(i)) % 60)
|
|
secondBuckets[idx].Store(0)
|
|
}
|
|
}
|
|
}
|
|
secIdx := int(sec % 60)
|
|
secondBuckets[secIdx].Add(1)
|
|
|
|
// 处理分钟级 bucket
|
|
oldMin := atomic.LoadInt64(&lastMinute)
|
|
if min != oldMin {
|
|
if atomic.CompareAndSwapInt64(&lastMinute, oldMin, min) {
|
|
steps := int(min - oldMin)
|
|
if steps > 60 { steps = 60 }
|
|
for i := 1; i <= steps; i++ {
|
|
idx := int((oldMin+int64(i)) % 60)
|
|
minuteBuckets[idx].Store(0)
|
|
}
|
|
}
|
|
}
|
|
minIdx := int(min % 60)
|
|
minuteBuckets[minIdx].Add(1)
|
|
|
|
totalRequests.Add(1)
|
|
}
|
|
|
|
// CurrentQPS 返回最近 1 秒(当前秒)的请求数
|
|
func CurrentQPS() int64 {
|
|
sec := time.Now().Unix()
|
|
if sec != atomic.LoadInt64(&lastSecond) { return 0 }
|
|
return secondBuckets[sec%60].Load()
|
|
}
|
|
|
|
// AvgQPSRecent60 返回最近 60 秒平均 QPS
|
|
func AvgQPSRecent60() float64 {
|
|
sec := time.Now().Unix()
|
|
total := int64(0)
|
|
last := atomic.LoadInt64(&lastSecond)
|
|
for i := 0; i < 60; i++ {
|
|
// 只统计在窗口内(未被清零)的 bucket
|
|
bucketSec := sec - int64(i)
|
|
if bucketSec <= last && last-bucketSec < 60 {
|
|
idx := bucketSec % 60
|
|
total += secondBuckets[idx].Load()
|
|
}
|
|
}
|
|
return float64(total) / 60.0
|
|
}
|
|
|
|
// CurrentQPM 返回当前分钟的请求数
|
|
func CurrentQPM() int64 {
|
|
min := time.Now().Unix() / 60
|
|
if min != atomic.LoadInt64(&lastMinute) { return 0 }
|
|
return minuteBuckets[min%60].Load()
|
|
}
|
|
|
|
// AvgQPMRecent60 返回最近 60 分钟的平均 QPM
|
|
func AvgQPMRecent60() float64 {
|
|
min := time.Now().Unix() / 60
|
|
total := int64(0)
|
|
last := atomic.LoadInt64(&lastMinute)
|
|
for i := 0; i < 60; i++ {
|
|
bucketMin := min - int64(i)
|
|
if bucketMin <= last && last-bucketMin < 60 {
|
|
idx := bucketMin % 60
|
|
total += minuteBuckets[idx].Load()
|
|
}
|
|
}
|
|
return float64(total) / 60.0
|
|
}
|
|
|
|
// TotalRequests 返回总请求量(从进程启动以来)
|
|
func TotalRequests() int64 { return totalRequests.Load() }
|
|
|
|
// MetricsHandler 输出当前指标
|
|
func MetricsHandler(c *gin.Context) {
|
|
c.JSON(200, gin.H{
|
|
"qps_current": CurrentQPS(),
|
|
"qps_avg_60s": AvgQPSRecent60(),
|
|
"qpm_current": CurrentQPM(),
|
|
"qpm_avg_60m": AvgQPMRecent60(),
|
|
"total": TotalRequests(),
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|