github.com/TeaOSLab/EdgeNode@v1.3.8/internal/stats/user_agent_parser.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 3 package stats 4 5 import ( 6 "github.com/TeaOSLab/EdgeNode/internal/goman" 7 "github.com/TeaOSLab/EdgeNode/internal/utils/fnv" 8 memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 9 syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync" 10 "github.com/mssola/useragent" 11 "sync" 12 "time" 13 ) 14 15 var SharedUserAgentParser = NewUserAgentParser() 16 17 const userAgentShardingCount = 8 18 19 // UserAgentParser UserAgent解析器 20 type UserAgentParser struct { 21 cacheMaps [userAgentShardingCount]map[uint64]UserAgentParserResult 22 pool *sync.Pool 23 mu *syncutils.RWMutex 24 25 maxCacheItems int 26 gcTicker *time.Ticker 27 gcIndex int 28 } 29 30 // NewUserAgentParser 获取新解析器 31 func NewUserAgentParser() *UserAgentParser { 32 var parser = &UserAgentParser{ 33 pool: &sync.Pool{ 34 New: func() any { 35 return &useragent.UserAgent{} 36 }, 37 }, 38 cacheMaps: [userAgentShardingCount]map[uint64]UserAgentParserResult{}, 39 mu: syncutils.NewRWMutex(userAgentShardingCount), 40 } 41 42 for i := 0; i < userAgentShardingCount; i++ { 43 parser.cacheMaps[i] = map[uint64]UserAgentParserResult{} 44 } 45 46 parser.init() 47 return parser 48 } 49 50 // 初始化 51 func (this *UserAgentParser) init() { 52 var maxCacheItems = 10_000 53 var systemMemory = memutils.SystemMemoryGB() 54 if systemMemory >= 16 { 55 maxCacheItems = 40_000 56 } else if systemMemory >= 8 { 57 maxCacheItems = 30_000 58 } else if systemMemory >= 4 { 59 maxCacheItems = 20_000 60 } 61 this.maxCacheItems = maxCacheItems 62 63 this.gcTicker = time.NewTicker(5 * time.Second) 64 goman.New(func() { 65 for range this.gcTicker.C { 66 this.GC() 67 } 68 }) 69 } 70 71 // Parse 解析UserAgent 72 func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResult) { 73 // 限制长度 74 if len(userAgent) == 0 || len(userAgent) > 256 { 75 return 76 } 77 78 var userAgentKey = fnv.HashString(userAgent) 79 var shardingIndex = int(userAgentKey % userAgentShardingCount) 80 81 this.mu.RLock(shardingIndex) 82 cacheResult, ok := this.cacheMaps[shardingIndex][userAgentKey] 83 if ok { 84 this.mu.RUnlock(shardingIndex) 85 return cacheResult 86 } 87 this.mu.RUnlock(shardingIndex) 88 89 var parser = this.pool.Get().(*useragent.UserAgent) 90 parser.Parse(userAgent) 91 result.OS = parser.OSInfo() 92 result.BrowserName, result.BrowserVersion = parser.Browser() 93 result.IsMobile = parser.Mobile() 94 this.pool.Put(parser) 95 96 // 忽略特殊字符 97 if len(result.BrowserName) > 0 { 98 for _, r := range result.BrowserName { 99 if r == '$' || r == '"' || r == '\'' || r == '<' || r == '>' || r == ')' { 100 return 101 } 102 } 103 } 104 105 this.mu.Lock(shardingIndex) 106 this.cacheMaps[shardingIndex][userAgentKey] = result 107 this.mu.Unlock(shardingIndex) 108 109 return 110 } 111 112 // MaxCacheItems 读取能容纳的缓存最大数量 113 func (this *UserAgentParser) MaxCacheItems() int { 114 return this.maxCacheItems 115 } 116 117 // Len 读取当前缓存数量 118 func (this *UserAgentParser) Len() int { 119 var total = 0 120 for i := 0; i < userAgentShardingCount; i++ { 121 this.mu.RLock(i) 122 total += len(this.cacheMaps[i]) 123 this.mu.RUnlock(i) 124 } 125 return total 126 } 127 128 // GC 回收多余的缓存 129 func (this *UserAgentParser) GC() { 130 var total = this.Len() 131 if total > this.maxCacheItems { 132 for { 133 var shardingIndex = this.gcIndex 134 135 this.mu.Lock(shardingIndex) 136 total -= len(this.cacheMaps[shardingIndex]) 137 this.cacheMaps[shardingIndex] = map[uint64]UserAgentParserResult{} 138 this.gcIndex = (this.gcIndex + 1) % userAgentShardingCount 139 this.mu.Unlock(shardingIndex) 140 141 if total <= this.maxCacheItems { 142 break 143 } 144 } 145 } 146 }