This commit is contained in:
jiazhizhong
2022-03-10 17:09:03 +08:00
commit 1279635d7f
97 changed files with 10632 additions and 0 deletions

278
pkg/base/stat.go Normal file
View File

@@ -0,0 +1,278 @@
package base
import (
"net/http"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
const (
minuteTimeLayout = "200601021504"
dateTimeLayout = "2006-01-02 15:04:05"
defaultReserveMinutes = 60
defaultCheckTimeMinutes = 10
)
// Stat 应用内统计
var Stat *stat
type (
stat struct {
ServerStartTime time.Time
EnableDetailRequestData bool
TotalRequestCount uint64
IntervalRequestData *Storage
DetailRequestURLData *Storage
TotalErrorCount uint64
IntervalErrorData *Storage
DetailErrorPageData *Storage
DetailErrorData *Storage
DetailHTTPCodeData *Storage
dataChanRequest chan *RequestInfo
dataChanError chan *ErrorInfo
dataChanHTTPCode chan *HttpCodeInfo
TotalConcurrentCount int64
infoPool *pool
}
pool struct {
requestInfo sync.Pool
errorInfo sync.Pool
httpCodeInfo sync.Pool
}
// RequestInfo 请求url信息
RequestInfo struct {
URL string
Code int
Num uint64
}
ErrorInfo struct {
URL string
ErrMsg string
Num uint64
}
HttpCodeInfo struct {
URL string
Code int
Num uint64
}
)
func (s *stat) QueryIntervalRequstData(key string) uint64 {
val, _ := s.IntervalRequestData.GetUint64(key)
return val
}
func (s *stat) QueryIntervalErrorData(key string) uint64 {
val, _ := s.IntervalErrorData.GetUint64(key)
return val
}
func (s *stat) AddRequestCount(page string, code int, num uint64) uint64 {
if !strings.HasPrefix(page, "/debug") {
atomic.AddUint64(&s.TotalRequestCount, num)
s.addRequestData(page, code, num)
s.addHTTPCodeData(page, code, num)
}
atomic.AddInt64(&s.TotalConcurrentCount, -1)
return atomic.LoadUint64(&s.TotalRequestCount)
}
func (s *stat) AddConcurrentCount() {
atomic.AddInt64(&s.TotalConcurrentCount, 1)
}
func (s *stat) AddErrorCount(page string, err error, num uint64) uint64 {
atomic.AddUint64(&s.TotalErrorCount, num)
s.addErrorData(page, err, num)
return atomic.LoadUint64(&s.TotalErrorCount)
}
func (s *stat) addRequestData(page string, code int, num uint64) {
info := s.infoPool.requestInfo.Get().(*RequestInfo)
info.URL = page
info.Code = code
info.Num = num
s.dataChanRequest <- info
}
func (s *stat) addErrorData(page string, err error, num uint64) {
info := s.infoPool.errorInfo.Get().(*ErrorInfo)
info.URL = page
info.ErrMsg = err.Error()
info.Num = num
s.dataChanError <- info
}
func (s *stat) addHTTPCodeData(page string, code int, num uint64) {
info := s.infoPool.httpCodeInfo.Get().(*HttpCodeInfo)
info.URL = page
info.Code = code
info.Num = num
s.dataChanHTTPCode <- info
}
func (s *stat) handleInfo() {
for {
select {
case info := <-s.dataChanRequest:
{
if s.EnableDetailRequestData {
if info.Code != http.StatusNotFound {
key := strings.ToLower(info.URL)
val, _ := s.DetailRequestURLData.GetUint64(key)
s.DetailRequestURLData.Store(key, val+info.Num)
}
}
key := time.Now().Format(minuteTimeLayout)
val, _ := s.IntervalRequestData.GetUint64(key)
s.IntervalRequestData.Store(key, val+info.Num)
s.infoPool.requestInfo.Put(info)
}
case info := <-s.dataChanError:
{
key := strings.ToLower(info.URL)
val, _ := s.DetailErrorPageData.GetUint64(key)
s.DetailErrorPageData.Store(key, val+info.Num)
key = info.ErrMsg
val, _ = s.DetailErrorData.GetUint64(key)
s.DetailErrorData.Store(key, val+info.Num)
key = time.Now().Format(minuteTimeLayout)
val, _ = s.IntervalErrorData.GetUint64(key)
s.IntervalErrorData.Store(key, val+info.Num)
s.infoPool.errorInfo.Put(info)
}
case info := <-s.dataChanHTTPCode:
{
key := strconv.Itoa(info.Code)
val, _ := s.DetailHTTPCodeData.GetUint64(key)
s.DetailHTTPCodeData.Store(key, val+info.Num)
s.infoPool.httpCodeInfo.Put(info)
}
}
}
}
func (s *stat) Collect() map[string]interface{} {
var dataMap = make(map[string]interface{})
dataMap["ServerStartTime"] = s.ServerStartTime.Format(dateTimeLayout)
dataMap["TotalRequestCount"] = atomic.LoadUint64(&s.TotalRequestCount)
dataMap["TotalConcurrentCount"] = atomic.LoadInt64(&s.TotalConcurrentCount)
dataMap["TotalErrorCount"] = s.TotalErrorCount
dataMap["IntervalRequestData"] = s.IntervalRequestData.All()
dataMap["DetailRequestUrlData"] = s.DetailRequestURLData.All()
dataMap["IntervalErrorData"] = s.IntervalErrorData.All()
dataMap["DetailErrorPageData"] = s.DetailErrorPageData.All()
dataMap["DetailErrorData"] = s.DetailErrorData.All()
dataMap["DetailHttpCodeData"] = s.DetailHTTPCodeData.All()
return dataMap
}
func (s *stat) gc() {
var needRemoveKey []string
now, _ := time.Parse(minuteTimeLayout, time.Now().Format(minuteTimeLayout))
if s.IntervalRequestData.Len() > defaultReserveMinutes {
s.IntervalRequestData.Range(func(key, val interface{}) bool {
keyString := key.(string)
if t, err := time.Parse(minuteTimeLayout, keyString); err != nil {
needRemoveKey = append(needRemoveKey, keyString)
} else {
if now.Sub(t) > (defaultReserveMinutes * time.Minute) {
needRemoveKey = append(needRemoveKey, keyString)
}
}
return true
})
}
for _, v := range needRemoveKey {
s.IntervalRequestData.Delete(v)
}
needRemoveKey = []string{}
if s.IntervalErrorData.Len() > defaultReserveMinutes {
s.IntervalErrorData.Range(func(key, val interface{}) bool {
keyString := key.(string)
if t, err := time.Parse(minuteTimeLayout, keyString); err != nil {
needRemoveKey = append(needRemoveKey, keyString)
} else {
if now.Sub(t) > defaultReserveMinutes*time.Minute {
needRemoveKey = append(needRemoveKey, keyString)
}
}
return true
})
}
for _, v := range needRemoveKey {
s.IntervalErrorData.Delete(v)
}
time.AfterFunc(time.Duration(defaultCheckTimeMinutes)*time.Minute, s.gc)
}
func init() {
Stat = &stat{
// 服务启动时间
ServerStartTime: time.Now(),
// 单位时间内请求数据 - 分钟为单位
IntervalRequestData: NewStorage(),
// 明细请求页面数据 - 以不带参数的访问url为key
DetailRequestURLData: NewStorage(),
// 单位时间内异常次数 - 按分钟为单位
IntervalErrorData: NewStorage(),
// 明细异常页面数据 - 以不带参数的访问url为key
DetailErrorPageData: NewStorage(),
// 单位时间内异常次数 - 按分钟为单位
DetailErrorData: NewStorage(),
// 明细Http状态码数据 - 以HttpCode为key例如200、500等
DetailHTTPCodeData: NewStorage(),
dataChanRequest: make(chan *RequestInfo, 1000),
dataChanError: make(chan *ErrorInfo, 1000),
dataChanHTTPCode: make(chan *HttpCodeInfo, 1000),
EnableDetailRequestData: true, //是否启用详细请求数据统计, 当url较多时导致内存占用过大
infoPool: &pool{
requestInfo: sync.Pool{
New: func() interface{} {
return &RequestInfo{}
},
},
errorInfo: sync.Pool{
New: func() interface{} {
return &ErrorInfo{}
},
},
httpCodeInfo: sync.Pool{
New: func() interface{} {
return &HttpCodeInfo{}
},
},
},
}
go Stat.handleInfo()
go time.AfterFunc(time.Duration(defaultCheckTimeMinutes)*time.Minute, Stat.gc)
}

53
pkg/base/storage.go Normal file
View File

@@ -0,0 +1,53 @@
package base
import (
"sync"
)
type (
Storage struct {
sync.Map
}
)
func NewStorage() *Storage {
return &Storage{}
}
func (s *Storage) All() map[string]interface{} {
data := make(map[string]interface{})
s.Range(func(key, value interface{}) bool {
data[key.(string)] = value
return true
})
return data
}
func (s *Storage) Exists(key interface{}) bool {
_, ok := s.Load(key)
return ok
}
func (s *Storage) GetUint64(key interface{}) (uint64, bool) {
val, ok := s.Load(key)
if !ok {
return 0, false
}
ret, ok := val.(uint64)
return ret, ok
}
func (s *Storage) Len() uint {
var count uint
s.Range(func(key, val interface{}) bool {
count++
return true
})
return count
}