Files
EdgeNode/internal/stats/user_agent_parser.go

147 lines
3.4 KiB
Go
Raw Normal View History

2022-01-01 21:47:59 +08:00
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package stats
import (
2024-01-12 16:30:32 +08:00
"github.com/TeaOSLab/EdgeNode/internal/goman"
2022-01-01 21:47:59 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils"
2023-05-20 10:38:17 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
2024-01-12 16:30:32 +08:00
syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
2023-05-20 11:52:27 +08:00
"github.com/mssola/useragent"
2022-01-06 17:05:04 +08:00
"sync"
2024-01-12 16:30:32 +08:00
"time"
2022-01-01 21:47:59 +08:00
)
2022-01-06 17:05:04 +08:00
var SharedUserAgentParser = NewUserAgentParser()
2024-01-12 16:30:32 +08:00
const userAgentShardingCount = 8
2022-01-01 21:47:59 +08:00
// UserAgentParser UserAgent解析器
type UserAgentParser struct {
2024-01-12 16:30:32 +08:00
cacheMaps [userAgentShardingCount]map[uint64]UserAgentParserResult
pool *sync.Pool
mu *syncutils.RWMutex
2022-01-01 21:47:59 +08:00
maxCacheItems int
2024-01-12 16:30:32 +08:00
gcTicker *time.Ticker
gcIndex int
2022-01-01 21:47:59 +08:00
}
2024-01-12 16:30:32 +08:00
// NewUserAgentParser 获取新解析器
2022-01-01 21:47:59 +08:00
func NewUserAgentParser() *UserAgentParser {
var parser = &UserAgentParser{
2024-01-12 16:30:32 +08:00
pool: &sync.Pool{
New: func() any {
return &useragent.UserAgent{}
},
},
cacheMaps: [userAgentShardingCount]map[uint64]UserAgentParserResult{},
mu: syncutils.NewRWMutex(userAgentShardingCount),
}
for i := 0; i < userAgentShardingCount; i++ {
parser.cacheMaps[i] = map[uint64]UserAgentParserResult{}
2022-01-01 21:47:59 +08:00
}
parser.init()
return parser
}
2024-01-12 16:30:32 +08:00
// 初始化
2022-01-01 21:47:59 +08:00
func (this *UserAgentParser) init() {
var maxCacheItems = 10_000
var systemMemory = utils.SystemMemoryGB()
if systemMemory >= 16 {
maxCacheItems = 40_000
} else if systemMemory >= 8 {
maxCacheItems = 30_000
} else if systemMemory >= 4 {
maxCacheItems = 20_000
}
this.maxCacheItems = maxCacheItems
2024-01-12 16:30:32 +08:00
this.gcTicker = time.NewTicker(5 * time.Second)
goman.New(func() {
for range this.gcTicker.C {
this.GC()
}
})
2022-01-01 21:47:59 +08:00
}
2024-01-12 16:30:32 +08:00
// Parse 解析UserAgent
2022-01-01 21:47:59 +08:00
func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResult) {
// 限制长度
if len(userAgent) == 0 || len(userAgent) > 256 {
return
}
2023-05-20 10:38:17 +08:00
var userAgentKey = fnv.HashString(userAgent)
2024-01-12 16:30:32 +08:00
var shardingIndex = int(userAgentKey % userAgentShardingCount)
2023-05-20 10:38:17 +08:00
2024-01-12 16:30:32 +08:00
this.mu.RLock(shardingIndex)
cacheResult, ok := this.cacheMaps[shardingIndex][userAgentKey]
2022-01-01 21:47:59 +08:00
if ok {
2024-01-12 16:30:32 +08:00
this.mu.RUnlock(shardingIndex)
2022-01-01 21:47:59 +08:00
return cacheResult
}
2024-01-12 16:30:32 +08:00
this.mu.RUnlock(shardingIndex)
2022-01-01 21:47:59 +08:00
2024-01-12 16:30:32 +08:00
var parser = this.pool.Get().(*useragent.UserAgent)
parser.Parse(userAgent)
result.OS = parser.OSInfo()
result.BrowserName, result.BrowserVersion = parser.Browser()
result.IsMobile = parser.Mobile()
this.pool.Put(parser)
2022-01-01 21:47:59 +08:00
2022-11-13 10:32:12 +08:00
// 忽略特殊字符
if len(result.BrowserName) > 0 {
for _, r := range result.BrowserName {
if r == '$' || r == '"' || r == '\'' || r == '<' || r == '>' || r == ')' {
return
}
}
}
2024-01-12 16:30:32 +08:00
this.mu.Lock(shardingIndex)
this.cacheMaps[shardingIndex][userAgentKey] = result
this.mu.Unlock(shardingIndex)
2022-01-01 21:47:59 +08:00
return
}
2024-01-12 16:30:32 +08:00
// MaxCacheItems 读取能容纳的缓存最大数量
func (this *UserAgentParser) MaxCacheItems() int {
return this.maxCacheItems
}
// Len 读取当前缓存数量
func (this *UserAgentParser) Len() int {
var total = 0
for i := 0; i < userAgentShardingCount; i++ {
this.mu.RLock(i)
total += len(this.cacheMaps[i])
this.mu.RUnlock(i)
}
return total
}
// GC 回收多余的缓存
func (this *UserAgentParser) GC() {
var total = this.Len()
if total > this.maxCacheItems {
for {
var shardingIndex = this.gcIndex
this.mu.Lock(shardingIndex)
total -= len(this.cacheMaps[shardingIndex])
this.cacheMaps[shardingIndex] = map[uint64]UserAgentParserResult{}
this.gcIndex = (this.gcIndex + 1) % userAgentShardingCount
this.mu.Unlock(shardingIndex)
if total <= this.maxCacheItems {
break
}
}
}
}