mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 16:00:25 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			147 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
						|
 | 
						|
package stats
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/TeaOSLab/EdgeNode/internal/goman"
 | 
						|
	"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
 | 
						|
	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
 | 
						|
	syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
 | 
						|
	"github.com/mssola/useragent"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
var SharedUserAgentParser = NewUserAgentParser()
 | 
						|
 | 
						|
const userAgentShardingCount = 8
 | 
						|
 | 
						|
// UserAgentParser UserAgent解析器
 | 
						|
type UserAgentParser struct {
 | 
						|
	cacheMaps [userAgentShardingCount]map[uint64]UserAgentParserResult
 | 
						|
	pool      *sync.Pool
 | 
						|
	mu        *syncutils.RWMutex
 | 
						|
 | 
						|
	maxCacheItems int
 | 
						|
	gcTicker      *time.Ticker
 | 
						|
	gcIndex       int
 | 
						|
}
 | 
						|
 | 
						|
// NewUserAgentParser 获取新解析器
 | 
						|
func NewUserAgentParser() *UserAgentParser {
 | 
						|
	var parser = &UserAgentParser{
 | 
						|
		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{}
 | 
						|
	}
 | 
						|
 | 
						|
	parser.init()
 | 
						|
	return parser
 | 
						|
}
 | 
						|
 | 
						|
// 初始化
 | 
						|
func (this *UserAgentParser) init() {
 | 
						|
	var maxCacheItems = 10_000
 | 
						|
	var systemMemory = memutils.SystemMemoryGB()
 | 
						|
	if systemMemory >= 16 {
 | 
						|
		maxCacheItems = 40_000
 | 
						|
	} else if systemMemory >= 8 {
 | 
						|
		maxCacheItems = 30_000
 | 
						|
	} else if systemMemory >= 4 {
 | 
						|
		maxCacheItems = 20_000
 | 
						|
	}
 | 
						|
	this.maxCacheItems = maxCacheItems
 | 
						|
 | 
						|
	this.gcTicker = time.NewTicker(5 * time.Second)
 | 
						|
	goman.New(func() {
 | 
						|
		for range this.gcTicker.C {
 | 
						|
			this.GC()
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Parse 解析UserAgent
 | 
						|
func (this *UserAgentParser) Parse(userAgent string) (result UserAgentParserResult) {
 | 
						|
	// 限制长度
 | 
						|
	if len(userAgent) == 0 || len(userAgent) > 256 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var userAgentKey = fnv.HashString(userAgent)
 | 
						|
	var shardingIndex = int(userAgentKey % userAgentShardingCount)
 | 
						|
 | 
						|
	this.mu.RLock(shardingIndex)
 | 
						|
	cacheResult, ok := this.cacheMaps[shardingIndex][userAgentKey]
 | 
						|
	if ok {
 | 
						|
		this.mu.RUnlock(shardingIndex)
 | 
						|
		return cacheResult
 | 
						|
	}
 | 
						|
	this.mu.RUnlock(shardingIndex)
 | 
						|
 | 
						|
	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)
 | 
						|
 | 
						|
	// 忽略特殊字符
 | 
						|
	if len(result.BrowserName) > 0 {
 | 
						|
		for _, r := range result.BrowserName {
 | 
						|
			if r == '$' || r == '"' || r == '\'' || r == '<' || r == '>' || r == ')' {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	this.mu.Lock(shardingIndex)
 | 
						|
	this.cacheMaps[shardingIndex][userAgentKey] = result
 | 
						|
	this.mu.Unlock(shardingIndex)
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |