github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/idles/run.go (about) 1 // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package idles 4 5 import ( 6 "encoding/json" 7 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 8 "github.com/TeaOSLab/EdgeNode/internal/goman" 9 fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 10 "github.com/iwind/TeaGo/Tea" 11 "github.com/shirou/gopsutil/v3/load" 12 "math" 13 "os" 14 "slices" 15 "sort" 16 "time" 17 ) 18 19 const maxSamples = 7 20 const cacheFile = "idle_hours.cache" 21 22 var hourlyLoadMap = map[int]*HourlyLoad{} 23 var sharedMinLoadHours []int 24 25 type HourlyLoad struct { 26 Hour int `json:"hour"` 27 Avg float64 `json:"avg"` 28 Values []float64 `json:"values"` 29 } 30 31 func init() { 32 if !teaconst.IsMain { 33 return 34 } 35 36 // recover from cache 37 { 38 data, err := os.ReadFile(Tea.Root + "/data/" + cacheFile) 39 if err == nil { 40 err = json.Unmarshal(data, &hourlyLoadMap) 41 if err == nil { 42 calculateMinLoadHours() 43 } 44 } 45 } 46 47 goman.New(func() { 48 var ticker = time.NewTicker(1 * time.Hour) 49 for range ticker.C { 50 CheckHourlyLoad(time.Now().Hour()) 51 } 52 }) 53 } 54 55 func CheckHourlyLoad(hour int) { 56 avgLoad, err := load.Avg() 57 if err != nil { 58 return 59 } 60 61 hourlyLoad, ok := hourlyLoadMap[hour] 62 if !ok { 63 hourlyLoad = &HourlyLoad{ 64 Hour: hour, 65 } 66 hourlyLoadMap[hour] = hourlyLoad 67 } 68 69 if len(hourlyLoad.Values) >= maxSamples { 70 hourlyLoad.Values = hourlyLoad.Values[:maxSamples-1] 71 } 72 hourlyLoad.Values = append(hourlyLoad.Values, avgLoad.Load15) 73 74 var sum float64 75 for _, v := range hourlyLoad.Values { 76 sum += v 77 } 78 hourlyLoad.Avg = math.Ceil(sum/float64(len(hourlyLoad.Values))*10) / 10 // fix precision 79 80 calculateMinLoadHours() 81 } 82 83 func Run(f func()) { 84 defer f() 85 86 var minLoadHours = sharedMinLoadHours // copy 87 88 if len(minLoadHours) == 0 { 89 fsutils.WaitLoad(15, 8, time.Hour) 90 return 91 } 92 93 var hour = time.Now().Hour() 94 var minLoadHour = -1 95 for _, v := range minLoadHours { 96 if v == hour { 97 minLoadHour = v 98 break 99 } 100 if v > hour { 101 minLoadHour = v 102 break 103 } 104 } 105 if minLoadHour < 0 { 106 minLoadHour = minLoadHours[0] 107 } 108 109 if minLoadHour == hour { 110 fsutils.WaitLoad(15, 10, time.Minute) 111 return 112 } 113 114 if minLoadHour < hour { 115 time.Sleep(time.Duration(24-hour+minLoadHour) * time.Hour) 116 } else { 117 time.Sleep(time.Duration(minLoadHour-hour) * time.Hour) 118 } 119 fsutils.WaitLoad(15, 10, time.Minute) 120 } 121 122 func RunTicker(ticker *time.Ticker, f func()) { 123 for range ticker.C { 124 Run(f) 125 } 126 } 127 128 func IsMinHour() bool { 129 var minLoadHours = sharedMinLoadHours // copy 130 return len(minLoadHours) > 0 && slices.Contains(minLoadHours, time.Now().Hour()) 131 } 132 133 func calculateMinLoadHours() { 134 var allLoads = []*HourlyLoad{} 135 for _, v := range hourlyLoadMap { 136 allLoads = append(allLoads, v) 137 } 138 139 sort.Slice(allLoads, func(i, j int) bool { 140 return allLoads[i].Avg < allLoads[j].Avg 141 }) 142 143 var minAvgLoad = allLoads[0].Avg 144 var newMinLoadHours []int 145 for _, v := range allLoads { 146 if v.Avg == minAvgLoad { 147 newMinLoadHours = append(newMinLoadHours, v.Hour) 148 } 149 } 150 sort.Ints(newMinLoadHours) 151 sharedMinLoadHours = newMinLoadHours 152 153 // write to cache 154 hourlyLoadMapJSON, err := json.Marshal(hourlyLoadMap) 155 if err == nil { 156 _ = os.WriteFile(Tea.Root+"/data/"+cacheFile, hourlyLoadMapJSON, 0666) 157 } 158 } 159 160 func TestMinLoadHours() []int { 161 return sharedMinLoadHours 162 } 163 164 func TestSetMinLoadHours(minLoadHours []int) { 165 sharedMinLoadHours = minLoadHours 166 } 167 168 func TestHourlyLoadMap() map[int]*HourlyLoad { 169 return hourlyLoadMap 170 }