some major stuff added more types added cpu usage and mhz per core
sophuwu sophie@sophuwu.com
Mon, 07 Jul 2025 10:24:08 +0200
5 files changed,
232 insertions(+),
89 deletions(-)
A
cmd/main.go
@@ -0,0 +1,12 @@
+package main + +import ( + "fmt" + "git.sophuwu.com/statlog" +) + +func main() { + hw := &statlog.HWInfo{} + hw.Update() + fmt.Println(hw) +}
M
device/cpu.go
→
device/cpu.go
@@ -6,13 +6,16 @@ "encoding/json"
"fmt" "git.sophuwu.com/statlog/types" "os" + "strings" "time" ) type CPU struct { - Load float64 - MHz float64 - Temp int + 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 (cpu *CPU) loadTemp() {@@ -64,76 +67,192 @@ }
if Err(err) { return } - cpu.Temp = (int(b[0])-48)*10 + int(b[1]) - 48 + cpu.Temp = (types.Celsius(b[0])-48)*10 + types.Celsius(b[1]) - 48 } func (c *CPU) loadMHz() { - c.MHz = 0 + c.MHzAvg = 0 b, err := os.ReadFile("/proc/cpuinfo") if err != nil { return } var ns types.NumSeeker - n := 0.0 + 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) - c.MHz += ns.GetFloat() - n++ + n = ns.GetFloat() + tot += n + c.MHzCore = append(c.MHzCore, types.MHz(n)) } } - c.MHz /= n + if len(c.MHzCore) == 0 { + c.MHzAvg = 0 + c.MHzCore = nil + return + } + c.MHzAvg = types.MHz(tot / float64(len(c.MHzCore))) } -func (c *CPU) loadUsage() { - readStat := func(n *[4]float64) bool { + +type coreLoad struct { + v [2][2]float64 +} - b := make([]byte, 100) - f, err := os.Open("/proc/stat") - if err != nil { - return true +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][1] - c.v[0][1]) / (c.v[1][0] - c.v[0][0]) +} + +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) } - _, err = f.Read(b) + 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 true + return err } - f.Close() - for i, j := 6, 0; j < 4; i++ { - if b[i] >= 48 && b[i] <= 57 { - n[j] = n[j]*10 + float64(b[i]) - 48 - } else if b[i] == ' ' { - j++ + 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() + for j = 0; j < 4 && i < len(b); i++ { + n = 0.0 + if b[i] >= 48 && b[i] <= 57 { + n = n*10 + float64(b[i]) - 48 + } else if b[i] == ' ' { + el.readStat(&t, &j, &n) + j++ + } else { + return fmt.Errorf("unexpected byte %c at index %d", b[i], i) + } + } + elsv() + for i < len(b) { + if b[i] == '\n' { + break + } + i++ } } - return false + return nil } - var a, b [4]float64 - if readStat(&a) { - return + err := loadVals(0) + if err != nil || len(cl) == 0 { + goto onErr } time.Sleep(types.SampleDuration) - if readStat(&b) { - return + err = loadVals(1) + if err != nil || len(cl) == 0 { + goto onErr + } + c.LoadAvg.FromFloat(cl[0].percent()) + c.LoadAvg.Clamp() + 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()) + c.LoadCore[i].Clamp() } - c.Load = ((b[0] + b[1] + b[2]) - (a[0] + a[1] + a[2])) / ((b[0] + b[1] + b[2] + b[3]) - (a[0] + a[1] + a[2] + a[3])) + return +onErr: + c.LoadAvg = -1.0 +onEmpty: + c.LoadCore = nil } -func (c *CPU) update() { + +func (c *CPU) Update() { c.loadMHz() c.loadTemp() - c.loadUsage() -} -func (c *CPU) GHzStr() string { - return fmt.Sprintf("%.2f GHz", c.MHz/1000) + c.loadUse() } -func (c *CPU) LoadStr() string { - return fmt.Sprintf("%.1f %c", c.Load*100, '%') + +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) TempStr() string { - if c.Temp == -100 { - return "" +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 fmt.Sprintf("%d C", c.Temp) + return } -func (c *CPU) String() string { - return fmt.Sprintf("%6.6s %8.8s %5.5s", c.LoadStr(), c.GHzStr(), c.TempStr()) + +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)
M
device/mem.go
→
device/mem.go
@@ -8,17 +8,17 @@ "os"
) type MEM struct { - Total types.Bytes - Free types.Bytes - Buffer types.Bytes - Used types.Bytes + Total types.Bytes `json:"Total"` + Free types.Bytes `json:"Free"` + Buffer types.Bytes `json:"Buffer"` + Used types.Bytes `json:"Used"` Percent struct { - Used types.Percent - WithBuff types.Percent - } + Used types.Percent `json:"Used"` + WithBuff types.Percent `json:"WithBuff"` + } `json:"Percent"` } -func (m *MEM) update() { +func (m *MEM) Update() { b := make([]byte, 140) f, _ := os.Open("/proc/meminfo") _, err := f.Read(b)@@ -28,16 +28,17 @@ }
f.Close() var seeker types.NumSeeker seeker.Init(b) - m.Total = types.Bytes(seeker.GetNum()) - m.Free = types.Bytes(seeker.GetNum()) - m.Used = m.Total - types.Bytes(seeker.GetNum()) - m.Buffer = types.Bytes(seeker.GetNum() + seeker.GetNum()) + m.Total.FromKiB(seeker.GetNum()) + m.Free.FromKiB(seeker.GetNum()) + m.Used.FromKiB(seeker.GetNum()) + m.Used = m.Total - m.Used + m.Buffer.FromKiB(seeker.GetNum() + seeker.GetNum()) m.Percent.Used.CalcBytes(m.Used, m.Total) m.Percent.WithBuff.CalcBytes(m.Used+m.Buffer, m.Total) } func (m *MEM) String() string { - return fmt.Sprintf("%s USED %s BUFF %s FREE", types.Bytes(m.Used), m.Buffer, m.Free) + return fmt.Sprintf("%s (%s) USED %s BUFF %s FREE", types.Bytes(m.Used), m.Percent.Used.String(), m.Buffer, m.Free) } func (m *MEM) JSON() (string, error) {
M
tmp.go
→
tmp.go
@@ -4,7 +4,6 @@ import (
"encoding/json" "fmt" "git.sophuwu.com/statlog/device" - "strings" "time" )@@ -14,36 +13,24 @@ CPU device.CPU `json:"CPU"`
MEM device.MEM `json:"MEM"` } -func (this *HWInfo) Update() { +func (hw *HWInfo) Update() { + hw.Time = time.Now().UnixMilli() done := make(chan bool) go func() { - this.CPU.update() + hw.CPU.Update() done <- true }() go func() { - this.MEM.update() + hw.MEM.Update() done <- true }() <-done <-done - this.Time = time.Now().UnixMilli() +} +func (hw *HWInfo) String() string { + return fmt.Sprintf("%s | %s\n%s", hw.MEM.String(), hw.CPU.String(), hw.CPU.LoadCoreStr()) } -func (i HWInfo) String() string { - if CONFIG.Json { - b, _ := json.Marshal(i) - return string(b) - } - s := i.MEM.String() - if SI[CONFIG.Unit] == '%' { - s = i.MEM.Percent().String() - } - return fmt.Sprintf("MEM: %s | CPU: %s", s, i.CPU.String()) +func (hw *HWInfo) JSON() (string, error) { + b, e := json.MarshalIndent(hw, "", " ") + return string(b), e } - -// func main() { -// var hw HWInfo -// for do := true; do; do = CONFIG.Repeat { -// hw.Update() -// fmt.Println(hw) -// } -// }
M
types/types.go
→
types/types.go
@@ -10,27 +10,26 @@
func (b Bytes) Human() string { n := float64(b) var i int - for i = units.B; i < units.GiB; i *= units.KiB { - if n < units.KiB { - break - } + for i = units.B; n >= units.KiB && i < units.GiB; i *= units.KiB { n /= units.KiB } + return fmt.Sprintf("%.2f %s", n, units.Labels[i]) } func (b Bytes) HumanSI() string { n := float64(b) var i int - for i = units.B; i < units.GB; i *= units.KB { - if n < units.KB { - break - } + for i = units.B; n >= units.KB && i < units.GB; i *= units.KB { n /= units.KB } return fmt.Sprintf("%.2f %s", n, units.Labels[i]) } +func (b *Bytes) FromKiB(v uint64) { + *b = Bytes(v * units.KiB) +} + func (b Bytes) Value() uint64 { return uint64(b) }@@ -41,9 +40,13 @@ }
type Percent float64 -func (p *Percent) String() string { - return fmt.Sprintf("%.1f%%", *p) +func (p Percent) String() string { + if p < 0 { + return "" + } + return fmt.Sprintf("%3.0f %%", p) } + func (p *Percent) Value() float64 { return float64(*p) }@@ -64,9 +67,30 @@ return
} *P = Percent(100 * Vn / Vmax) } +func (P *Percent) FromFloat(v float64) { + *P = Percent(v * 100) +} func (P *Percent) SetValue(v float64) { *P = Percent(v) } func (P *Percent) SetValueInt(v int) { *P = Percent(v) } + +type MHz int + +func (m MHz) String() string { + if m < 0 { + return "" + } + return fmt.Sprintf("%4d MHz", m) +} + +type Celsius int + +func (c Celsius) String() string { + if c <= -50 { + return "" + } + return fmt.Sprintf("%3d C", c) +}