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) }