194 lines
4.8 KiB
Go
194 lines
4.8 KiB
Go
|
package processor
|
||
|
|
||
|
import (
|
||
|
"github.com/shirou/gopsutil/cpu"
|
||
|
"strconv"
|
||
|
"path/filepath"
|
||
|
"fmt"
|
||
|
"errors"
|
||
|
"time"
|
||
|
"sync"
|
||
|
"path"
|
||
|
"amuz.es/src/infra/cpu_ctrl/logger"
|
||
|
"io/ioutil"
|
||
|
"strings"
|
||
|
"amuz.es/src/infra/cpu_ctrl/pid"
|
||
|
"amuz.es/src/infra/cpu_ctrl/util"
|
||
|
)
|
||
|
|
||
|
type processor struct {
|
||
|
handler util.Handler
|
||
|
id int
|
||
|
tempeturePath string
|
||
|
tempeture float64
|
||
|
tempetureChanged chan float64
|
||
|
sampleDuration time.Duration
|
||
|
fanController pid.Controller
|
||
|
fanSpeed int
|
||
|
fanMaxSpeed int
|
||
|
fanMinSpeed int
|
||
|
fanSpeedChanged chan int
|
||
|
}
|
||
|
type Processor interface {
|
||
|
Id() int
|
||
|
Tempeture() float64
|
||
|
FanSpeed() int
|
||
|
FanMaxSpeed() int
|
||
|
FanMinSpeed() int
|
||
|
StartMonitoring()
|
||
|
TempetureNotifier() <-chan float64
|
||
|
FanSpeedNotifier() <-chan int
|
||
|
getMaxTempetureOnProcessor(string) (float64)
|
||
|
readTempeture(string, chan<- float64, *sync.WaitGroup)
|
||
|
normalizeFanspeed(float64) (int)
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
log = logger.NewLogger("processor")
|
||
|
)
|
||
|
|
||
|
func GetProcessorCount() (maxcpu int) {
|
||
|
stat, err := cpu.Info()
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
for _, info := range stat {
|
||
|
physicalId, err := strconv.Atoi(info.PhysicalID)
|
||
|
if err != nil {
|
||
|
return
|
||
|
} else if maxcpu < physicalId {
|
||
|
maxcpu = physicalId
|
||
|
}
|
||
|
}
|
||
|
maxcpu += 1
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func NewProcessorInfo(
|
||
|
handler util.Handler,
|
||
|
processorId int,
|
||
|
sampleDuration time.Duration,
|
||
|
P, I, D,
|
||
|
setPoint float64,
|
||
|
maxNoob, minNoob int,
|
||
|
) (Processor, error) {
|
||
|
|
||
|
if matches, err := filepath.Glob(fmt.Sprintf("/sys/devices/platform/coretemp.%d/hwmon/hwmon?", processorId)); err != nil {
|
||
|
return nil, err
|
||
|
} else if matches == nil {
|
||
|
return nil, errors.New("hwmon not found!")
|
||
|
} else {
|
||
|
windupGuard := float64(maxNoob - minNoob)
|
||
|
|
||
|
controller := pid.New(P, I, D)
|
||
|
controller.SetSetPoint(setPoint)
|
||
|
controller.SetSampleTime(sampleDuration)
|
||
|
controller.SetWindupGuard(windupGuard)
|
||
|
|
||
|
return &processor{
|
||
|
handler: handler,
|
||
|
id: processorId,
|
||
|
tempeturePath: matches[0],
|
||
|
tempetureChanged: make(chan float64),
|
||
|
sampleDuration: sampleDuration,
|
||
|
fanController: controller,
|
||
|
fanSpeedChanged: make(chan int),
|
||
|
fanMaxSpeed: maxNoob,
|
||
|
fanMinSpeed: minNoob,
|
||
|
}, nil
|
||
|
}
|
||
|
}
|
||
|
func (p *processor) Id() int { return p.id }
|
||
|
func (p *processor) Tempeture() float64 { return p.tempeture }
|
||
|
func (p *processor) FanSpeed() int { return p.fanSpeed }
|
||
|
func (p *processor) FanMaxSpeed() int { return p.fanMaxSpeed }
|
||
|
func (p *processor) FanMinSpeed() int { return p.fanMinSpeed }
|
||
|
func (p *processor) TempetureNotifier() <-chan float64 { return p.tempetureChanged }
|
||
|
func (p *processor) FanSpeedNotifier() <-chan int { return p.fanSpeedChanged }
|
||
|
|
||
|
func (p *processor) StartMonitoring() {
|
||
|
defer p.handler.DecreaseWait()
|
||
|
defer func() {
|
||
|
if err := recover(); err != nil {
|
||
|
p.handler.NotifyError(err.(error))
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
defer close(p.tempetureChanged)
|
||
|
defer close(p.fanSpeedChanged)
|
||
|
|
||
|
tempeturePathGlob := path.Join(p.tempeturePath, "temp?_input")
|
||
|
ticker := time.Tick(p.sampleDuration)
|
||
|
log.Infof("Processor %d monitor started with %s", p.id, p.sampleDuration)
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case <-ticker:
|
||
|
var (
|
||
|
highestTemp float64
|
||
|
fanspeed int
|
||
|
)
|
||
|
|
||
|
highestTemp = p.getMaxTempetureOnProcessor(tempeturePathGlob)
|
||
|
if highestTemp != p.tempeture {
|
||
|
p.tempetureChanged <- highestTemp
|
||
|
p.tempeture = highestTemp
|
||
|
}
|
||
|
|
||
|
fanspeed = p.normalizeFanspeed(p.fanController.Update(highestTemp))
|
||
|
if fanspeed != p.fanSpeed {
|
||
|
p.fanSpeedChanged <- fanspeed
|
||
|
p.fanSpeed = fanspeed
|
||
|
}
|
||
|
|
||
|
case <-p.handler.Done():
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *processor) getMaxTempetureOnProcessor(tempeturePathGlob string) (maxTempeture float64) {
|
||
|
matches, err := filepath.Glob(tempeturePathGlob)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
tempetureReadWaiter := &sync.WaitGroup{}
|
||
|
queue := make(chan float64, len(matches))
|
||
|
defer close(queue)
|
||
|
|
||
|
// exclude package temp
|
||
|
for _, tempetureFilePath := range matches[1:] {
|
||
|
tempetureReadWaiter.Add(1)
|
||
|
go p.readTempeture(tempetureFilePath, queue, tempetureReadWaiter)
|
||
|
}
|
||
|
tempetureReadWaiter.Wait()
|
||
|
for sense := range queue {
|
||
|
if maxTempeture < sense {
|
||
|
maxTempeture = sense
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (p *processor) readTempeture(path string, senseChan chan<- float64, waiter *sync.WaitGroup) {
|
||
|
defer waiter.Done()
|
||
|
if dat, err := ioutil.ReadFile(path); err != nil {
|
||
|
p.handler.NotifyError(err)
|
||
|
} else if tempetureSense, err := strconv.Atoi(strings.TrimSpace(string(dat))); err != nil {
|
||
|
p.handler.NotifyError(err)
|
||
|
} else {
|
||
|
senseChan <- float64(tempetureSense) / 1000.0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *processor) normalizeFanspeed(response float64) (adjusted int) {
|
||
|
adjusted = int(-response)
|
||
|
if adjusted < p.fanMinSpeed {
|
||
|
adjusted = p.fanMinSpeed
|
||
|
} else if adjusted > p.fanMaxSpeed {
|
||
|
adjusted = p.fanMaxSpeed
|
||
|
}
|
||
|
return
|
||
|
}
|