270 lines
5.1 KiB
Go
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)
|
|
}
|