From be2e961fb122e9944056d94acdee01e8b8587725 Mon Sep 17 00:00:00 2001 From: Sangbum Kim Date: Tue, 5 Sep 2017 01:13:03 +0900 Subject: [PATCH] added initial files --- .gitignore | 124 ++++++++++++++++++++++++++++ cpu_ctrl.service | 26 ++++++ main.go | 206 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 .gitignore create mode 100644 cpu_ctrl.service create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27f6a34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ + +# Created by https://www.gitignore.io/api/intellij,go,linux,osx,windows + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea +*.iml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + +### Go ### +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + + +### Linux ### +*~ +*.swp + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +build/ +vendor/ + diff --git a/cpu_ctrl.service b/cpu_ctrl.service new file mode 100644 index 0000000..69c6ac5 --- /dev/null +++ b/cpu_ctrl.service @@ -0,0 +1,26 @@ +[Unit] +Description=IPMI Cpu fan control +Documentation=https://amuz.es/src/infra/cpu_ctrl +After=syslog.target + +[Service] +Type=notify +Restart=always +RestartSec=15 +ExecStart=/usr/bin/cpu_ctrl +TimeoutStartSec=5 + + +# Disable timeout logic and wait until process is stopped +TimeoutStopSec=0 +# SIGTERM signal is used to stop Minio +KillSignal=SIGTERM +SendSIGKILL=no + +SuccessExitStatus=0 +# kill only the docker process, not all processes in the cgroup +KillMode=process + +[Install] +WantedBy=default.target + diff --git a/main.go b/main.go new file mode 100644 index 0000000..68cb921 --- /dev/null +++ b/main.go @@ -0,0 +1,206 @@ +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} +) + + +const ( + DaemonStarted notifyType = "READY=1" + DaemonStopping notifyType = "STOPPING=1" +) + +var sockPath string + +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 + } +} +