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  }