github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/cachehits/stat.go (about)

     1  // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
     2  
     3  package cachehits
     4  
     5  import (
     6  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     7  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
     8  	"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
     9  	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
    10  	"github.com/iwind/TeaGo/Tea"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  )
    15  
    16  const countSamples = 100_000
    17  
    18  type Item struct {
    19  	countHits   uint64
    20  	countCached uint64
    21  	timestamp   int64
    22  
    23  	isGood bool
    24  	isBad  bool
    25  }
    26  
    27  type Stat struct {
    28  	goodRatio uint64
    29  	maxItems  int
    30  
    31  	itemMap map[string]*Item // category => *Item
    32  	mu      *sync.RWMutex
    33  
    34  	ticker *time.Ticker
    35  }
    36  
    37  func NewStat(goodRatio uint64) *Stat {
    38  	if goodRatio == 0 {
    39  		goodRatio = 5
    40  	}
    41  
    42  	var maxItems = memutils.SystemMemoryGB() * 10_000
    43  	if maxItems <= 0 {
    44  		maxItems = 100_000
    45  	}
    46  
    47  	var stat = &Stat{
    48  		goodRatio: goodRatio,
    49  		itemMap:   map[string]*Item{},
    50  		mu:        &sync.RWMutex{},
    51  		ticker:    time.NewTicker(24 * time.Hour),
    52  		maxItems:  maxItems,
    53  	}
    54  
    55  	goman.New(func() {
    56  		stat.init()
    57  	})
    58  	return stat
    59  }
    60  
    61  func (this *Stat) init() {
    62  	idles.RunTicker(this.ticker, func() {
    63  		var currentTime = fasttime.Now().Unix()
    64  
    65  		this.mu.RLock()
    66  		for _, item := range this.itemMap {
    67  			if item.timestamp < currentTime-7*24*86400 {
    68  				// reset
    69  				item.countHits = 0
    70  				item.countCached = 1
    71  				item.timestamp = currentTime
    72  				item.isGood = false
    73  				item.isBad = false
    74  			}
    75  		}
    76  		this.mu.RUnlock()
    77  	})
    78  }
    79  
    80  func (this *Stat) IncreaseCached(category string) {
    81  	this.mu.RLock()
    82  	var item = this.itemMap[category]
    83  	if item != nil {
    84  		if item.isGood || item.isBad {
    85  			this.mu.RUnlock()
    86  			return
    87  		}
    88  
    89  		atomic.AddUint64(&item.countCached, 1)
    90  		this.mu.RUnlock()
    91  		return
    92  	}
    93  	this.mu.RUnlock()
    94  
    95  	this.mu.Lock()
    96  
    97  	if len(this.itemMap) > this.maxItems {
    98  		// remove one randomly
    99  		for k := range this.itemMap {
   100  			delete(this.itemMap, k)
   101  			break
   102  		}
   103  	}
   104  
   105  	this.itemMap[category] = &Item{
   106  		countHits:   0,
   107  		countCached: 1,
   108  		timestamp:   fasttime.Now().Unix(),
   109  	}
   110  	this.mu.Unlock()
   111  }
   112  
   113  func (this *Stat) IncreaseHit(category string) {
   114  	this.mu.RLock()
   115  	defer this.mu.RUnlock()
   116  
   117  	var item = this.itemMap[category]
   118  	if item != nil {
   119  		if item.isGood || item.isBad {
   120  			return
   121  		}
   122  
   123  		atomic.AddUint64(&item.countHits, 1)
   124  		return
   125  	}
   126  }
   127  
   128  func (this *Stat) IsGood(category string) bool {
   129  	this.mu.RLock()
   130  	defer func() {
   131  		this.mu.RUnlock()
   132  	}()
   133  
   134  	var item = this.itemMap[category]
   135  	if item != nil {
   136  		if item.isBad {
   137  			return false
   138  		}
   139  		if item.isGood {
   140  			return true
   141  		}
   142  
   143  		if item.countCached > countSamples && (Tea.IsTesting() || item.timestamp < fasttime.Now().Unix()-600) /** 10 minutes ago **/ {
   144  			var isGood = item.countHits*100/item.countCached >= this.goodRatio
   145  			if isGood {
   146  				item.isGood = true
   147  			} else {
   148  				item.isBad = true
   149  			}
   150  
   151  			return isGood
   152  		}
   153  	}
   154  
   155  	return true
   156  }
   157  
   158  func (this *Stat) Len() int {
   159  	this.mu.RLock()
   160  	defer this.mu.RUnlock()
   161  
   162  	return len(this.itemMap)
   163  }