package device import ( "bytes" "encoding/json" "fmt" "git.sophuwu.com/statlog/types" "os" "strings" "time" ) type CPU struct { LoadAvg types.Percent `json:"LoadAvg"` LoadCore []types.Percent `json:"LoadCore"` MHzAvg types.MHz `json:"MHzAvg"` MHzCore []types.MHz `json:"MHzCore"` Temp types.Celsius `json:"Temp"` } func (c *CPU) LoadBar() (s string, err error) { w, _ := types.TermSize() l := len(c.LoadCore) ss := "" div := 3 if w < 100 { div = 2 } else if w > 200 { div = 4 } w = (w - 2) / div for i := 0; i < l; i++ { if ss, err = c.LoadCore[i].Bar(fmt.Sprintf("%2d", i), w); err != nil { return "", err } s += ss if i%div == div-1 { s += "\n" } else { s += " " } } if s[len(s)-1] == ' ' || s[len(s)-1] == '\n' { s = s[:len(s)-1] } return s, nil } func (cpu *CPU) loadTemp() { if cpu.Temp == -100 { return } var findCPUTypeNo = func(path string, fileName string, comp string) (string, error) { var b = make([]byte, 100) var i int = 0 if fileName == "_label" { i = 1 } var err error = nil for ; err == nil && string(b[:len(comp)]) != comp; i++ { b, err = os.ReadFile(path + string(i+48) + fileName) } if err != nil { return "", err } return string(i + 48 - 1), nil } Err := func(err error) bool { if err == nil { return false } cpu.Temp = -100 return true } var ( b = make([]byte, 2) thrmPath = "/sys/class/thermal/thermal_zone" hwMonPath = "/sys/class/hwmon/hwmon" ) nStr, err := findCPUTypeNo(thrmPath, "/type", "x86_pkg_temp") if err != nil { nStr, err = findCPUTypeNo(hwMonPath, "/name", "k10temp") if Err(err) { return } nStrHW := nStr nStr, err = findCPUTypeNo(hwMonPath+nStrHW+"/temp", "_label", "Tdie") if Err(err) { return } b, err = os.ReadFile(hwMonPath + nStrHW + "/temp" + nStr + "_input") } else { b, err = os.ReadFile(thrmPath + nStr + "/temp") } if Err(err) { return } cpu.Temp = (types.Celsius(b[0])-48)*10 + types.Celsius(b[1]) - 48 } func (c *CPU) loadMHz() { c.MHzAvg = 0 b, err := os.ReadFile("/proc/cpuinfo") if err != nil { return } var ns types.NumSeeker c.MHzCore = make([]types.MHz, 0) tot, n := 0.0, 0.0 for _, v := range bytes.Split(b, []byte("\n")) { if bytes.HasPrefix(v, []byte("cpu MHz")) { ns.Init(v) n = ns.GetFloat() tot += n c.MHzCore = append(c.MHzCore, types.MHz(n)) } } if len(c.MHzCore) == 0 { c.MHzAvg = 0 c.MHzCore = nil return } c.MHzAvg = types.MHz(tot / float64(len(c.MHzCore))) } type coreLoad struct { v [2][2]float64 } func (c *coreLoad) readStat(t, i *int, n *float64) { *n += c.v[*t][0] if *i == 3 { c.v[*t][1] = *n return } c.v[*t][0] = *n } func (c *coreLoad) percent() float64 { if c.v[0][1] == 0 { return 0.0 } return (c.v[1][0] - c.v[0][0]) / (c.v[1][1] - c.v[0][1]) } func (c *CPU) loadUse() { var cl []coreLoad loadVals := func(t int) error { i, j, k, n := 0, 0, 0, 0.0 var el coreLoad elget := func() { el = coreLoad{[2][2]float64{{0.0, 0.0}, {0.0, 0.0}}} } elsv := func() { cl = append(cl, el) } if t >= 1 { t = 1 elget = func() { el = cl[k] } elsv = func() { cl[k] = el k++ } } b, err := os.ReadFile("/proc/stat") if err != nil { return err } for i = 0; i < len(b)-3 && (t == 0 || k < len(cl)); i++ { if !(b[i] == 'c' && b[i+1] == 'p' && b[i+2] == 'u') { break } for i < len(b) { if b[i] >= '0' && b[i] <= '9' { break } i++ } elget() n = 0.0 for j = 0; j < 4 && i < len(b); i++ { if b[i] >= 48 && b[i] <= 57 { n = n*10 + float64(b[i]) - 48 } else if b[i] == ' ' { el.readStat(&t, &j, &n) n = 0.0 j++ } else { return fmt.Errorf("unexpected byte %c at index %d", b[i], i) } } if j < 3 { return fmt.Errorf("unexpected end of line at index %d", i) } elsv() for i < len(b) { if b[i] == '\n' { break } i++ } } return nil } err := loadVals(0) if err != nil || len(cl) == 0 { goto onErr } time.Sleep(types.SampleDuration) err = loadVals(1) if err != nil || len(cl) == 0 { goto onErr } c.LoadAvg.FromFloat(cl[0].percent()) cl = cl[1:] if len(cl) == 0 { goto onEmpty } c.LoadCore = make([]types.Percent, len(cl)) for i, j := range cl { c.LoadCore[i].FromFloat(j.percent()) } return onErr: c.LoadAvg = -1.0 onEmpty: c.LoadCore = nil } func (c *CPU) Update() { c.loadMHz() c.loadTemp() c.loadUse() } func numSize(n int) int { if n == 0 { return 1 } i := 0 if n < 0 { n *= -1 i++ } for n > 0 { i++ n /= 10 } return i } func (c *CPU) LoadCoreStr() (s string) { if c.LoadCore == nil || len(c.LoadCore) == 0 { return s } var i int fmtstr := "cpu %." + fmt.Sprintf("%d", numSize(len(c.LoadCore))) + "d: %s" fn := func() { s += fmt.Sprintf(fmtstr, i, c.LoadCore[i]) } if c.MHzCore != nil && len(c.LoadCore) == len(c.MHzCore) { fmtstr += " %s" fn = func() { s += fmt.Sprintf(fmtstr, i, c.LoadCore[i], c.MHzCore[i]) } } for i = 0; i < len(c.LoadCore); i++ { fn() if i%3 == 2 { s += "\n" } else { s += " | " } } if strings.HasSuffix(s, "\n") { s = s[:len(s)-1] } else if strings.HasSuffix(s, " | ") { s = s[:len(s)-5] } return } func (c CPU) String() string { return fmt.Sprintf("%s %s %s", c.LoadAvg, c.MHzAvg, c.Temp) } func (c *CPU) JSON() (string, error) { b, e := json.Marshal(c) return string(b), e }