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  }