Files
2026-04-22 16:12:50 +02:00

270 lines
5.1 KiB
Go

package main
import (
"bufio"
"fmt"
"math"
"os"
"os/exec"
"strconv"
"strings"
tea "charm.land/bubbletea/v2"
)
type SysValue[T int | float64] struct {
Val T
Fmt string
Inf bool
}
type Detail struct {
Name string
Desc string
Memory SysValue[float64]
MemoryMax SysValue[float64]
Tasks SysValue[int]
TasksMax SysValue[int]
Cpu float64
Active string
Load string
Sub string
}
type DetailLoadedMsg struct {
Detail Detail
Err error
}
func (m *model) LoadDetail() tea.Msg {
d, err := m.loadDetail()
if err != nil {
return DetailLoadedMsg{Err: err}
}
if d == nil {
return DetailLoadedMsg{}
}
cpu, _ := strconv.ParseFloat(d["CPUUsageNSec"], 64)
return DetailLoadedMsg{Detail: Detail{
Name: m.filtered[m.cursor].Name,
Desc: m.filtered[m.cursor].Desc,
Memory: take[float64](d, "MemoryCurrent", "", "B", 2, 1000),
MemoryMax: take[float64](d, "MemoryMax", "", "B", 2, 1000),
Tasks: take[int](d, "TasksCurrent", "", "", 1, 1000),
TasksMax: take[int](d, "TasksMax", "", "", 1, 1000),
Cpu: cpu / 1000000000,
Active: d["ActiveState"],
Load: d["LoadState"],
Sub: d["SubState"],
}}
}
func take[T int | float64](d map[string]string, key string, start string, unit string, decimals int, step int) SysValue[T] {
val, _ := takeVal[T](d, key, start)
inf := normUnit(d, key, start) == ""
fmt := formatUnit(d, key, start, unit, decimals, step)
return SysValue[T]{Val: val, Inf: inf, Fmt: fmt}
}
func (v SysValue[T]) Alt(alt SysValue[T]) SysValue[T] {
if v.Inf {
return alt
}
return v
}
func (v SysValue[T]) Div(y SysValue[T]) float64 {
return float64(v.Val) / float64(y.Val)
}
func takeVal[T int | float64](d map[string]string, key string, start string) (T, error) {
x := normUnit(d, key, start)
var zero T
switch any(zero).(type) {
case int:
v, err := strconv.Atoi(x)
return any(v).(T), err
case float64:
v, err := strconv.ParseFloat(x, 64)
return any(v).(T), err
default:
return zero, fmt.Errorf("unsupported type")
}
}
func (m *model) loadDetail() (map[string]string, error) {
if len(m.filtered) == 0 {
return nil, nil
}
detail := make(map[string]string)
name := m.filtered[m.cursor].Name
cmd := exec.Command(
"systemctl", "show", name,
"-p", "CPUUsageNSec",
"-p", "CPUQuotaPerSecUSec",
"-p", "MemoryCurrent",
"-p", "MemoryMax",
"-p", "TasksCurrent",
"-p", "TasksMax",
"-p", "ActiveState",
"-p", "LoadState",
"-p", "SubState",
)
out, err := cmd.Output()
if err != nil {
return nil, err
}
lines := strings.SplitSeq(string(out), "\n")
for line := range lines {
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
detail[parts[0]] = parts[1]
}
}
return detail, nil
}
type System struct {
Memory SysValue[float64]
Tasks SysValue[int]
}
type SystemLoadedMsg struct {
System System
Err error
}
func LoadSystem() tea.Msg {
d, err := loadMemSystem()
if err != nil {
return SystemLoadedMsg{Err: err}
}
if d == nil {
return SystemLoadedMsg{}
}
return SystemLoadedMsg{System: System{
Memory: take[float64](d, "MemTotal", "K", "B", 2, 1000),
Tasks: take[int](d, "TasksMax", "", "", 1, 1000),
}}
}
func loadMemSystem() (map[string]string, error) {
f, err := os.Open("/proc/meminfo")
if err != nil {
return nil, err
}
defer f.Close()
l := make(map[string]string)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
key := strings.TrimSuffix(parts[0], ":")
val, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
l[key] = strconv.FormatUint(val, 10)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return l, nil
}
func normUnit(dic map[string]string, key string, start string) string {
start = strings.ToUpper(start)
st := dic[key]
if st == "infinity" || st == "" {
return ""
}
sizes := []string{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}
n := 0
for i, s := range sizes {
if s == start {
n = i
break
}
}
return fmt.Sprintf("%s%s", dic[key], strings.Repeat("0", n*3))
}
func formatUnit(dic map[string]string, key string, start string, unit string, decimals int, step int) string {
start = strings.ToUpper(start)
fstep := float64(step)
st := dic[key]
if st == "infinity" || st == "" {
return fmt.Sprintf("∞ %s", unit)
}
value, _ := strconv.ParseFloat(st, 64)
if value == 0 {
return fmt.Sprintf("0 %s%s", start, unit)
}
sizes := []string{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}
n := 0
for i, s := range sizes {
if s == start {
n = i
break
}
}
i := n + int(math.Floor(math.Log(value)/math.Log(fstep)))
if i < 0 {
i = 0
}
if i >= len(sizes) {
i = len(sizes) - 1
}
value = value / math.Pow(fstep, float64(i-n))
if math.Abs(value-math.Round(value)) < 0.05 {
value = math.Round(value)
}
var formatted string
if math.Mod(value, 1) == 0 {
formatted = fmt.Sprintf("%.0f", value)
} else {
format := fmt.Sprintf("%%.%df", decimals)
formatted = fmt.Sprintf(format, value)
}
return fmt.Sprintf("%s %s%s", formatted, sizes[i], unit)
}