package main import ( "context" "fmt" "io/ioutil" "os" "os/exec" "os/signal" "path" "path/filepath" "strconv" "strings" "sync" "syscall" "time" "github.com/coreos/go-systemd/daemon" "github.com/shirou/gopsutil/cpu" ) const ( auto = 0x0 min = 0x04 max = 0x64 ) var ( prefix = [...]int{0x3a, 0x01} suffix = [...]int{auto, auto, auto, auto, auto, auto} ) type notifyType string const ( DaemonStarted notifyType = "READY=1" DaemonStopping notifyType = "STOPPING=1" ) func NotifyDaemon(status notifyType) { daemon.SdNotify(false, string(status)) } func getProcessorCount() int { var maxcpu int stat, err := cpu.Info() if err != nil { panic(err) } for _, info := range stat { physicalId, err := strconv.Atoi(info.PhysicalID) if err != nil { panic(err) } else if maxcpu < physicalId { maxcpu = physicalId } } return maxcpu + 1 } type Processor struct { Id int TempeturePath string } type TempetureChange struct { Id int Tempeture float64 } func getProcessorInfo(processorId int) *Processor { matches, err := filepath.Glob(fmt.Sprintf("/sys/devices/platform/coretemp.%d/hwmon/hwmon?", processorId)) if err != nil { panic(err) } return &Processor{ Id: processorId, TempeturePath: matches[0], } } func ReadTempeture(path string, senseChan chan<- float64, waiter *sync.WaitGroup) { defer waiter.Done() dat, err := ioutil.ReadFile(path) if err != nil { senseChan <- 0.0 return } tempetureSense, err := strconv.Atoi(strings.TrimSpace(string(dat))) if err != nil { senseChan <- 0.0 return } senseChan <- float64(tempetureSense) / 1000.0 return } func CpuTempetureMonitoring(info *Processor, notifier chan<- TempetureChange, ctx context.Context, waiter *sync.WaitGroup) { waiter.Add(1) defer waiter.Done() tempeturePathGlob := path.Join(info.TempeturePath, "temp?_input") ticker := time.Tick(3 * time.Second) for { select { case <-ticker: matches, err := filepath.Glob(tempeturePathGlob) if err != nil { panic(err) } tempetureReadWaiter := &sync.WaitGroup{} queue := make(chan float64, len(matches)) // exclude package temp for _, path := range matches[1:] { tempetureReadWaiter.Add(1) go ReadTempeture(path, queue, tempetureReadWaiter) } tempetureReadWaiter.Wait() close(queue) var tempeture float64 for sense := range queue { if tempeture < sense { tempeture = sense } } notifier <- TempetureChange{ Id: info.Id, Tempeture: tempeture, } case <-ctx.Done(): return } } } func CpuTempetureScraper(processorCount int, notifier <-chan TempetureChange, ctx context.Context, waiter *sync.WaitGroup) { waiter.Add(1) defer waiter.Done() pastFan := make([]int, processorCount) for { select { case change := <-notifier: delta := int(change.Tempeture) - 33 fan := 0x4 + (delta - delta%0x4) if fan < 0x4 { fan = 0x4 } else if fan > 0x50 { fan = 0x0 } if pastFan[change.Id] != fan { pastFan[change.Id] = fan } else { continue } fmt.Printf("cpu %d fan 0x%x\n", change.Id, fan) args := make([]string, 0) args = append(args, "raw", "0x3a", "0x01", ) for _, item := range pastFan { args = append(args, fmt.Sprintf("0x%x", item)) } args = append(args, "0x0", "0x0", "0x0", "0x0", "0x0", "0x0", ) cmd := exec.Command("ipmitool", args...) if err := cmd.Run(); err != nil { fmt.Printf(err.Error()) } case <-ctx.Done(): return } } } func main() { var ( processorCount = getProcessorCount() ctx, canceled = context.WithCancel(context.Background()) waiter = &sync.WaitGroup{} exitSignal = make(chan os.Signal, 1) tempetureChange = make(chan TempetureChange) ) for i := 0; i < processorCount; i++ { info := getProcessorInfo(i) go CpuTempetureMonitoring(info, tempetureChange, ctx, waiter) } go CpuTempetureScraper(processorCount, tempetureChange, ctx, waiter) defer waiter.Wait() signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) NotifyDaemon(DaemonStarted) defer NotifyDaemon(DaemonStopping) select { case <-ctx.Done(): fmt.Println("Service request to close this application") return case sysSignal := <-exitSignal: canceled() fmt.Printf("SYSCALL! %s", sysSignal.String()) break } }