github.com/lingyao2333/mo-zero@v1.4.1/core/stat/metrics.go (about) 1 package stat 2 3 import ( 4 "os" 5 "sync" 6 "time" 7 8 "github.com/lingyao2333/mo-zero/core/executors" 9 "github.com/lingyao2333/mo-zero/core/logx" 10 "github.com/lingyao2333/mo-zero/core/syncx" 11 ) 12 13 var ( 14 logInterval = time.Minute 15 writerLock sync.Mutex 16 reportWriter Writer = nil 17 logEnabled = syncx.ForAtomicBool(true) 18 ) 19 20 type ( 21 // Writer interface wraps the Write method. 22 Writer interface { 23 Write(report *StatReport) error 24 } 25 26 // A StatReport is a stat report entry. 27 StatReport struct { 28 Name string `json:"name"` 29 Timestamp int64 `json:"tm"` 30 Pid int `json:"pid"` 31 ReqsPerSecond float32 `json:"qps"` 32 Drops int `json:"drops"` 33 Average float32 `json:"avg"` 34 Median float32 `json:"med"` 35 Top90th float32 `json:"t90"` 36 Top99th float32 `json:"t99"` 37 Top99p9th float32 `json:"t99p9"` 38 } 39 40 // A Metrics is used to log and report stat reports. 41 Metrics struct { 42 executor *executors.PeriodicalExecutor 43 container *metricsContainer 44 } 45 ) 46 47 // DisableLog disables logs of stats. 48 func DisableLog() { 49 logEnabled.Set(false) 50 } 51 52 // SetReportWriter sets the report writer. 53 func SetReportWriter(writer Writer) { 54 writerLock.Lock() 55 reportWriter = writer 56 writerLock.Unlock() 57 } 58 59 // NewMetrics returns a Metrics. 60 func NewMetrics(name string) *Metrics { 61 container := &metricsContainer{ 62 name: name, 63 pid: os.Getpid(), 64 } 65 66 return &Metrics{ 67 executor: executors.NewPeriodicalExecutor(logInterval, container), 68 container: container, 69 } 70 } 71 72 // Add adds task to m. 73 func (m *Metrics) Add(task Task) { 74 m.executor.Add(task) 75 } 76 77 // AddDrop adds a drop to m. 78 func (m *Metrics) AddDrop() { 79 m.executor.Add(Task{ 80 Drop: true, 81 }) 82 } 83 84 // SetName sets the name of m. 85 func (m *Metrics) SetName(name string) { 86 m.executor.Sync(func() { 87 m.container.name = name 88 }) 89 } 90 91 type ( 92 tasksDurationPair struct { 93 tasks []Task 94 duration time.Duration 95 drops int 96 } 97 98 metricsContainer struct { 99 name string 100 pid int 101 tasks []Task 102 duration time.Duration 103 drops int 104 } 105 ) 106 107 func (c *metricsContainer) AddTask(v interface{}) bool { 108 if task, ok := v.(Task); ok { 109 if task.Drop { 110 c.drops++ 111 } else { 112 c.tasks = append(c.tasks, task) 113 c.duration += task.Duration 114 } 115 } 116 117 return false 118 } 119 120 func (c *metricsContainer) Execute(v interface{}) { 121 pair := v.(tasksDurationPair) 122 tasks := pair.tasks 123 duration := pair.duration 124 drops := pair.drops 125 size := len(tasks) 126 report := &StatReport{ 127 Name: c.name, 128 Timestamp: time.Now().Unix(), 129 Pid: c.pid, 130 ReqsPerSecond: float32(size) / float32(logInterval/time.Second), 131 Drops: drops, 132 } 133 134 if size > 0 { 135 report.Average = float32(duration/time.Millisecond) / float32(size) 136 137 fiftyPercent := size >> 1 138 if fiftyPercent > 0 { 139 top50pTasks := topK(tasks, fiftyPercent) 140 medianTask := top50pTasks[0] 141 report.Median = float32(medianTask.Duration) / float32(time.Millisecond) 142 tenPercent := fiftyPercent / 5 143 if tenPercent > 0 { 144 top10pTasks := topK(tasks, tenPercent) 145 task90th := top10pTasks[0] 146 report.Top90th = float32(task90th.Duration) / float32(time.Millisecond) 147 onePercent := tenPercent / 10 148 if onePercent > 0 { 149 top1pTasks := topK(top10pTasks, onePercent) 150 task99th := top1pTasks[0] 151 report.Top99th = float32(task99th.Duration) / float32(time.Millisecond) 152 pointOnePercent := onePercent / 10 153 if pointOnePercent > 0 { 154 topPointOneTasks := topK(top1pTasks, pointOnePercent) 155 task99Point9th := topPointOneTasks[0] 156 report.Top99p9th = float32(task99Point9th.Duration) / float32(time.Millisecond) 157 } else { 158 report.Top99p9th = getTopDuration(top1pTasks) 159 } 160 } else { 161 mostDuration := getTopDuration(top10pTasks) 162 report.Top99th = mostDuration 163 report.Top99p9th = mostDuration 164 } 165 } else { 166 mostDuration := getTopDuration(tasks) 167 report.Top90th = mostDuration 168 report.Top99th = mostDuration 169 report.Top99p9th = mostDuration 170 } 171 } else { 172 mostDuration := getTopDuration(tasks) 173 report.Median = mostDuration 174 report.Top90th = mostDuration 175 report.Top99th = mostDuration 176 report.Top99p9th = mostDuration 177 } 178 } 179 180 log(report) 181 } 182 183 func (c *metricsContainer) RemoveAll() interface{} { 184 tasks := c.tasks 185 duration := c.duration 186 drops := c.drops 187 c.tasks = nil 188 c.duration = 0 189 c.drops = 0 190 191 return tasksDurationPair{ 192 tasks: tasks, 193 duration: duration, 194 drops: drops, 195 } 196 } 197 198 func getTopDuration(tasks []Task) float32 { 199 top := topK(tasks, 1) 200 if len(top) < 1 { 201 return 0 202 } 203 204 return float32(top[0].Duration) / float32(time.Millisecond) 205 } 206 207 func log(report *StatReport) { 208 writeReport(report) 209 if logEnabled.True() { 210 logx.Statf("(%s) - qps: %.1f/s, drops: %d, avg time: %.1fms, med: %.1fms, "+ 211 "90th: %.1fms, 99th: %.1fms, 99.9th: %.1fms", 212 report.Name, report.ReqsPerSecond, report.Drops, report.Average, report.Median, 213 report.Top90th, report.Top99th, report.Top99p9th) 214 } 215 } 216 217 func writeReport(report *StatReport) { 218 writerLock.Lock() 219 defer writerLock.Unlock() 220 221 if reportWriter != nil { 222 if err := reportWriter.Write(report); err != nil { 223 logx.Error(err) 224 } 225 } 226 }