Processes tab: btop-style cpu/mem/net boxes with labels, gradient bars, block graph, CPU model/GHz/temp, memory breakdown, per-interface rates, and a refresh interval control
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e4df878ac0
commit
b802dcf313
+86
-15
@@ -37,9 +37,12 @@ const (
|
||||
)
|
||||
|
||||
type Mem struct {
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Avail uint64 `json:"avail"`
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Avail uint64 `json:"avail"`
|
||||
Free uint64 `json:"free,omitempty"`
|
||||
Cached uint64 `json:"cached,omitempty"`
|
||||
Buffers uint64 `json:"buffers,omitempty"`
|
||||
}
|
||||
|
||||
type Temp struct {
|
||||
@@ -379,7 +382,7 @@ func meminfo() (Mem, Mem) {
|
||||
n, _ := strconv.ParseUint(f[0], 10, 64)
|
||||
m[k] = n * 1024
|
||||
}
|
||||
mem := Mem{Total: m["MemTotal"], Avail: m["MemAvailable"]}
|
||||
mem := Mem{Total: m["MemTotal"], Avail: m["MemAvailable"], Free: m["MemFree"], Buffers: m["Buffers"], Cached: m["Cached"] + m["SReclaimable"]}
|
||||
if mem.Avail == 0 { // pre-3.14 kernels: approximate
|
||||
mem.Avail = m["MemFree"] + m["Buffers"] + m["Cached"]
|
||||
}
|
||||
@@ -885,15 +888,69 @@ func uidName(uid string) string {
|
||||
|
||||
// ProcTable is the Processes tab payload.
|
||||
type ProcTable struct {
|
||||
T int64 `json:"t"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Cores []float64 `json:"cores"`
|
||||
Load [3]float64 `json:"load"`
|
||||
Mem Mem `json:"mem"`
|
||||
Swap Mem `json:"swap"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
Procs []Proc `json:"procs"`
|
||||
Total int `json:"total"`
|
||||
T int64 `json:"t"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Cores []float64 `json:"cores"`
|
||||
CPUHist []float64 `json:"cpu_hist"` // recent total-CPU samples, oldest first
|
||||
CPUModel string `json:"cpu_model,omitempty"`
|
||||
CPUMHz float64 `json:"cpu_mhz,omitempty"`
|
||||
CPUTemp float64 `json:"cpu_temp,omitempty"`
|
||||
Load [3]float64 `json:"load"`
|
||||
Mem Mem `json:"mem"`
|
||||
Swap Mem `json:"swap"`
|
||||
Net []Net `json:"net"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
Procs []Proc `json:"procs"`
|
||||
Total int `json:"total"`
|
||||
Running int `json:"running"`
|
||||
}
|
||||
|
||||
var cpuModel = func() string {
|
||||
for _, line := range strings.Split(readStr("/proc/cpuinfo"), "\n") {
|
||||
if strings.HasPrefix(line, "model name") || strings.HasPrefix(line, "Model") || strings.HasPrefix(line, "Hardware") {
|
||||
if _, v, ok := strings.Cut(line, ":"); ok {
|
||||
return strings.Join(strings.Fields(v), " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
|
||||
func cpuMHz() float64 {
|
||||
var sum float64
|
||||
n := 0
|
||||
for _, line := range strings.Split(readStr("/proc/cpuinfo"), "\n") {
|
||||
if strings.HasPrefix(line, "cpu MHz") {
|
||||
if _, v, ok := strings.Cut(line, ":"); ok {
|
||||
f, _ := strconv.ParseFloat(strings.TrimSpace(v), 64)
|
||||
sum += f
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 0 { // ARM etc.: scaling_cur_freq in kHz
|
||||
if v, ok := readInt("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"); ok {
|
||||
return float64(v) / 1000
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return sum / float64(n)
|
||||
}
|
||||
|
||||
// cpuTemp picks the sensor that best represents the CPU package.
|
||||
func cpuTemp(temps []Temp) float64 {
|
||||
best, max := 0.0, 0.0
|
||||
for _, t := range temps {
|
||||
l := strings.ToLower(t.Label)
|
||||
if t.Chip == "coretemp" && strings.HasPrefix(l, "package") || t.Chip == "k10temp" && (l == "tctl" || l == "tdie") ||
|
||||
t.Chip == "thermal" && strings.Contains(l, "cpu") || t.Chip == "cpu_thermal" || t.Chip == "soc_thermal" {
|
||||
return t.C
|
||||
}
|
||||
if (t.Chip == "coretemp" || t.Chip == "k10temp") && t.C > max {
|
||||
max, best = t.C, t.C
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
var bootTime = func() int64 {
|
||||
@@ -997,10 +1054,24 @@ func (c *Collector) Processes() ProcTable {
|
||||
}
|
||||
return procs[i].RSS > procs[j].RSS
|
||||
})
|
||||
t := ProcTable{T: now.UnixMilli(), Procs: procs, Total: len(procs), Uptime: uptime(), Load: loadavg()}
|
||||
t := ProcTable{T: now.UnixMilli(), Procs: procs, Total: len(procs), Uptime: uptime(), Load: loadavg(),
|
||||
CPUModel: cpuModel, CPUMHz: round1(cpuMHz()), CPUHist: []float64{}, Net: []Net{}}
|
||||
for _, p := range procs {
|
||||
if p.State == "R" {
|
||||
t.Running++
|
||||
}
|
||||
}
|
||||
t.Mem, t.Swap = meminfo()
|
||||
if n := len(c.ring); n > 0 {
|
||||
t.CPU, t.Cores = c.ring[n-1].CPU, c.ring[n-1].Cores
|
||||
last := c.ring[n-1]
|
||||
t.CPU, t.Cores, t.Net, t.CPUTemp = last.CPU, last.Cores, last.Net, cpuTemp(last.Temps)
|
||||
from := n - 90
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
for _, s := range c.ring[from:] {
|
||||
t.CPUHist = append(t.CPUHist, s.CPU)
|
||||
}
|
||||
}
|
||||
c.lastTable = &t
|
||||
return t
|
||||
|
||||
Reference in New Issue
Block a user