You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
5.0 KiB
Go
227 lines
5.0 KiB
Go
package stat
|
|
|
|
import (
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/zeromicro/go-zero/core/executors"
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
"github.com/zeromicro/go-zero/core/syncx"
|
|
)
|
|
|
|
var (
|
|
logInterval = time.Minute
|
|
writerLock sync.Mutex
|
|
reportWriter Writer = nil
|
|
logEnabled = syncx.ForAtomicBool(true)
|
|
)
|
|
|
|
type (
|
|
// Writer interface wraps the Write method.
|
|
Writer interface {
|
|
Write(report *StatReport) error
|
|
}
|
|
|
|
// A StatReport is a stat report entry.
|
|
StatReport struct {
|
|
Name string `json:"name"`
|
|
Timestamp int64 `json:"tm"`
|
|
Pid int `json:"pid"`
|
|
ReqsPerSecond float32 `json:"qps"`
|
|
Drops int `json:"drops"`
|
|
Average float32 `json:"avg"`
|
|
Median float32 `json:"med"`
|
|
Top90th float32 `json:"t90"`
|
|
Top99th float32 `json:"t99"`
|
|
Top99p9th float32 `json:"t99p9"`
|
|
}
|
|
|
|
// A Metrics is used to log and report stat reports.
|
|
Metrics struct {
|
|
executor *executors.PeriodicalExecutor
|
|
container *metricsContainer
|
|
}
|
|
)
|
|
|
|
// DisableLog disables logs of stats.
|
|
func DisableLog() {
|
|
logEnabled.Set(false)
|
|
}
|
|
|
|
// SetReportWriter sets the report writer.
|
|
func SetReportWriter(writer Writer) {
|
|
writerLock.Lock()
|
|
reportWriter = writer
|
|
writerLock.Unlock()
|
|
}
|
|
|
|
// NewMetrics returns a Metrics.
|
|
func NewMetrics(name string) *Metrics {
|
|
container := &metricsContainer{
|
|
name: name,
|
|
pid: os.Getpid(),
|
|
}
|
|
|
|
return &Metrics{
|
|
executor: executors.NewPeriodicalExecutor(logInterval, container),
|
|
container: container,
|
|
}
|
|
}
|
|
|
|
// Add adds task to m.
|
|
func (m *Metrics) Add(task Task) {
|
|
m.executor.Add(task)
|
|
}
|
|
|
|
// AddDrop adds a drop to m.
|
|
func (m *Metrics) AddDrop() {
|
|
m.executor.Add(Task{
|
|
Drop: true,
|
|
})
|
|
}
|
|
|
|
// SetName sets the name of m.
|
|
func (m *Metrics) SetName(name string) {
|
|
m.executor.Sync(func() {
|
|
m.container.name = name
|
|
})
|
|
}
|
|
|
|
type (
|
|
tasksDurationPair struct {
|
|
tasks []Task
|
|
duration time.Duration
|
|
drops int
|
|
}
|
|
|
|
metricsContainer struct {
|
|
name string
|
|
pid int
|
|
tasks []Task
|
|
duration time.Duration
|
|
drops int
|
|
}
|
|
)
|
|
|
|
func (c *metricsContainer) AddTask(v any) bool {
|
|
if task, ok := v.(Task); ok {
|
|
if task.Drop {
|
|
c.drops++
|
|
} else {
|
|
c.tasks = append(c.tasks, task)
|
|
c.duration += task.Duration
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *metricsContainer) Execute(v any) {
|
|
pair := v.(tasksDurationPair)
|
|
tasks := pair.tasks
|
|
duration := pair.duration
|
|
drops := pair.drops
|
|
size := len(tasks)
|
|
report := &StatReport{
|
|
Name: c.name,
|
|
Timestamp: time.Now().Unix(),
|
|
Pid: c.pid,
|
|
ReqsPerSecond: float32(size) / float32(logInterval/time.Second),
|
|
Drops: drops,
|
|
}
|
|
|
|
if size > 0 {
|
|
report.Average = float32(duration/time.Millisecond) / float32(size)
|
|
|
|
fiftyPercent := size >> 1
|
|
if fiftyPercent > 0 {
|
|
top50pTasks := topK(tasks, fiftyPercent)
|
|
medianTask := top50pTasks[0]
|
|
report.Median = float32(medianTask.Duration) / float32(time.Millisecond)
|
|
tenPercent := fiftyPercent / 5
|
|
if tenPercent > 0 {
|
|
top10pTasks := topK(tasks, tenPercent)
|
|
task90th := top10pTasks[0]
|
|
report.Top90th = float32(task90th.Duration) / float32(time.Millisecond)
|
|
onePercent := tenPercent / 10
|
|
if onePercent > 0 {
|
|
top1pTasks := topK(top10pTasks, onePercent)
|
|
task99th := top1pTasks[0]
|
|
report.Top99th = float32(task99th.Duration) / float32(time.Millisecond)
|
|
pointOnePercent := onePercent / 10
|
|
if pointOnePercent > 0 {
|
|
topPointOneTasks := topK(top1pTasks, pointOnePercent)
|
|
task99Point9th := topPointOneTasks[0]
|
|
report.Top99p9th = float32(task99Point9th.Duration) / float32(time.Millisecond)
|
|
} else {
|
|
report.Top99p9th = getTopDuration(top1pTasks)
|
|
}
|
|
} else {
|
|
mostDuration := getTopDuration(top10pTasks)
|
|
report.Top99th = mostDuration
|
|
report.Top99p9th = mostDuration
|
|
}
|
|
} else {
|
|
mostDuration := getTopDuration(tasks)
|
|
report.Top90th = mostDuration
|
|
report.Top99th = mostDuration
|
|
report.Top99p9th = mostDuration
|
|
}
|
|
} else {
|
|
mostDuration := getTopDuration(tasks)
|
|
report.Median = mostDuration
|
|
report.Top90th = mostDuration
|
|
report.Top99th = mostDuration
|
|
report.Top99p9th = mostDuration
|
|
}
|
|
}
|
|
|
|
log(report)
|
|
}
|
|
|
|
func (c *metricsContainer) RemoveAll() any {
|
|
tasks := c.tasks
|
|
duration := c.duration
|
|
drops := c.drops
|
|
c.tasks = nil
|
|
c.duration = 0
|
|
c.drops = 0
|
|
|
|
return tasksDurationPair{
|
|
tasks: tasks,
|
|
duration: duration,
|
|
drops: drops,
|
|
}
|
|
}
|
|
|
|
func getTopDuration(tasks []Task) float32 {
|
|
top := topK(tasks, 1)
|
|
if len(top) < 1 {
|
|
return 0
|
|
}
|
|
|
|
return float32(top[0].Duration) / float32(time.Millisecond)
|
|
}
|
|
|
|
func log(report *StatReport) {
|
|
writeReport(report)
|
|
if logEnabled.True() {
|
|
logx.Statf("(%s) - qps: %.1f/s, drops: %d, avg time: %.1fms, med: %.1fms, "+
|
|
"90th: %.1fms, 99th: %.1fms, 99.9th: %.1fms",
|
|
report.Name, report.ReqsPerSecond, report.Drops, report.Average, report.Median,
|
|
report.Top90th, report.Top99th, report.Top99p9th)
|
|
}
|
|
}
|
|
|
|
func writeReport(report *StatReport) {
|
|
writerLock.Lock()
|
|
defer writerLock.Unlock()
|
|
|
|
if reportWriter != nil {
|
|
if err := reportWriter.Write(report); err != nil {
|
|
logx.Error(err)
|
|
}
|
|
}
|
|
}
|