Files
2026-05-20 16:13:58 +08:00

244 lines
5.5 KiB
Go

package statutils
import (
"encoding/json"
"fmt"
"runtime"
"sort"
"stat/common/utils"
"sync"
"time"
"unsafe"
)
var ServerMon = Monitor{}
type ErrorRecord struct {
Error error `json:"error"`
Occur time.Time `json:"occur"`
}
type JobItem struct {
Name string `json:"name"`
Call string `json:"call"`
Count int64 `json:"count"`
Start time.Time `json:"start"`
Stop time.Time `json:"stop"`
Avg time.Duration `json:"avg"`
Gap time.Duration `json:"gap"`
Error error `json:"error"`
SuccCount int64 `json:"succ_count"`
ErrorCount int64 `json:"error_count"`
Errors []*ErrorRecord `json:"errors"`
}
func SpendTimeIcon(d time.Duration) string {
if d < 5*time.Second {
return "\U0001F7E2\U0001F7E2\U0001F7E2"
} else if d < 10*time.Second {
return "\U0001F7E2\U0001F7E2\U0001F7E0"
} else if d < 30*time.Second {
return "\U0001F7E2\U0001F7E0\U0001F7E0"
} else if d < 80*time.Second {
return "\U0001F7E0\U0001F7E0\U0001F7E0"
} else if d < 220*time.Second {
return "\U0001F7E0\U0001F7E0🔴"
} else if d < 600*time.Second {
return "\U0001F7E0🔴🔴"
} else if d < 1640*time.Second {
return "🔴🔴🔴"
} else {
return "🆘🆘🆘"
}
}
func (s *JobItem) Status() string {
if s.Count > s.SuccCount+s.ErrorCount {
return "💛执行中"
} else {
if s.Error != nil {
return "💔执行出错"
} else {
return "💚执行成功"
}
}
}
func (s *JobItem) Display() string {
text := fmt.Sprintf("【函数】%s\n", s.Name)
text += fmt.Sprintf("【调用】%s\n", s.Call)
text += fmt.Sprintf("【进入】%d次\n", s.Count)
text += fmt.Sprintf("【状态】%s\n", s.Status())
text += fmt.Sprintf("【开始】%s\n", s.Start.Format("15:04:05"))
text += fmt.Sprintf("【结束】%s\n", s.Stop.Format("15:04:05"))
text += fmt.Sprintf("【用时】%s\n", s.Stop.Sub(s.Start))
text += fmt.Sprintf("【速度】%s\n", SpendTimeIcon(s.Stop.Sub(s.Start)))
text += fmt.Sprintf("【平均】%s\n", s.Avg)
text += fmt.Sprintf("【间隔】%s\n", s.Gap)
text += fmt.Sprintf("【成功】%d次\n", s.SuccCount)
text += fmt.Sprintf("【出错】%d次\n", s.ErrorCount)
if len(s.Errors) > 0 {
text += fmt.Sprintf("【最前出错信息】🔻%s\n", s.Errors[0].Error.Error())
}
if len(s.Errors) > 1 {
text += fmt.Sprintf("【最后出错信息】🔺%s\n", s.Errors[len(s.Errors)-1].Error.Error())
}
return text
}
func newJob(name string, call string) *JobItem {
now := time.Now()
return &JobItem{
Name: name,
Call: call,
Count: 1,
Start: now,
Stop: now,
Errors: make([]*ErrorRecord, 0),
}
}
func (s *JobItem) Begin() {
now := time.Now()
if s.Count > 0 {
s.Gap = now.Sub(s.Start)
}
s.Start = now
s.Count++
}
func (s *JobItem) End(err error) {
now := time.Now()
s.Error = err
s.Stop = now
if err != nil {
s.ErrorCount++
s.Errors = append(s.Errors, &ErrorRecord{
Error: err,
Occur: now,
})
} else {
s.SuccCount++
s.Avg = time.Duration(int64(s.Avg+(s.Stop.Sub(s.Start))) / s.SuccCount)
}
}
type Monitor struct {
lock sync.Mutex
jobs map[string]*JobItem
}
func (s *Monitor) call(callFunc func() error, funcName string, callName string) error {
func() {
s.lock.Lock()
defer s.lock.Unlock()
if s.jobs == nil {
s.jobs = make(map[string]*JobItem)
}
job, ok := s.jobs[funcName]
if !ok {
job = newJob(funcName, callName)
s.jobs[funcName] = job
} else {
job.Begin()
}
}()
err := callFunc()
func() {
s.lock.Lock()
defer s.lock.Unlock()
job := s.jobs[funcName]
if job != nil {
job.End(err)
}
}()
return err
}
func (s *Monitor) Run(fn func() error) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
funcName := GetFuncName(fn)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn()
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) RunWithName(fn func() error, funcName string) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn()
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) Run1(fn func(v time.Time) error, param time.Time) error {
defer utils.RecoverError(nil)
callName := GetCallName(2)
funcName := GetFuncName(fn)
call := func() (err error) {
defer utils.RecoverError(func(err1 error) {
err = err1
})
err = fn(param)
return err
}
return s.call(call, funcName, callName)
}
func (s *Monitor) ToJson() string {
s.lock.Lock()
defer s.lock.Unlock()
data, _ := json.Marshal(s.jobs)
return string(data)
}
func (s *Monitor) Display() string {
s.lock.Lock()
defer s.lock.Unlock()
list := make(JobItemList, 0)
for _, job := range s.jobs {
list = append(list, job)
}
sort.Sort(list)
text := fmt.Sprintf("后台自动任务(%d个):\n", len(s.jobs))
for _, job := range list {
text += job.Display()
text += "***** *****\n"
}
return text
}
func GetCallName(skip int) string {
pc, _, _, _ := runtime.Caller(skip)
return runtime.FuncForPC(pc).Name()
}
func GetFuncName(fn interface{}) string {
funcPC := func(fn interface{}) uintptr {
ptr := (*[2]uintptr)(unsafe.Pointer(&fn))[1]
return **((**uintptr)(unsafe.Pointer(&ptr)))
}
pc := funcPC(fn)
return runtime.FuncForPC(pc).Name()
}
type JobItemList []*JobItem
func (a JobItemList) Len() int { return len(a) }
func (a JobItemList) Less(i, j int) bool {
return a[i].Name < a[j].Name
}
func (a JobItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }