mirror of
https://codeberg.org/gigirassy/energonfetch.git
synced 2026-08-30 15:27:40 +00:00
Add main.go
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var cube = []string{
|
||||
" +------+",
|
||||
" / /|",
|
||||
" +------+ |",
|
||||
" | | +",
|
||||
" | |/",
|
||||
" +------+",
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
info := collectAll()
|
||||
|
||||
values := make([]string, 0, len(info))
|
||||
keys := make([]string, 0, len(info))
|
||||
for _, kv := range info {
|
||||
values = append(values, strings.ToLower(kv.v))
|
||||
keys = append(keys, strings.ToLower(kv.k))
|
||||
}
|
||||
|
||||
printAligned(values, keys, cube)
|
||||
}
|
||||
|
||||
type pair struct{ k, v string }
|
||||
|
||||
func collectAll() []pair {
|
||||
var out []pair
|
||||
|
||||
distro := readPrettyOSRelease("/etc/os-release")
|
||||
if distro == "" {
|
||||
distro = runtime.GOOS
|
||||
}
|
||||
out = append(out, pair{"os", distro})
|
||||
|
||||
out = append(out, pair{"arch", runtime.GOARCH})
|
||||
|
||||
out = append(out, pair{"kernel", getUname()})
|
||||
|
||||
cpuModel, cpuHz := detectCPU()
|
||||
if cpuHz > 0 {
|
||||
|
||||
if cpuHz >= 1e9 {
|
||||
ghz := float64(cpuHz) / 1e9
|
||||
out = append(out, pair{"cpu", fmt.Sprintf("%s-%.2f ghz", cpuModel, ghz)})
|
||||
} else {
|
||||
out = append(out, pair{"cpu", fmt.Sprintf("%s-%d mhz", cpuModel, cpuHz/1e6)})
|
||||
}
|
||||
} else {
|
||||
out = append(out, pair{"cpu", cpuModel})
|
||||
}
|
||||
|
||||
memTotal, memAvail, swapTotal, swapFree := parseMemInfo()
|
||||
if memTotal > 0 {
|
||||
memUsed := memTotal - memAvail
|
||||
out = append(out, pair{"memory", fmt.Sprintf("%s / %s", hr(memUsed), hr(memTotal))})
|
||||
}
|
||||
if swapTotal > 0 {
|
||||
swapUsed := swapTotal - swapFree
|
||||
out = append(out, pair{"swap", fmt.Sprintf("%s / %s", hr(swapUsed), hr(swapTotal))})
|
||||
}
|
||||
|
||||
out = append(out, pair{"desktop", detectDesktop()})
|
||||
|
||||
used, total, fstype := getStorageAndType("/")
|
||||
out = append(out, pair{"storage", fmt.Sprintf("%s / %s %s", hr(used), hr(total), fstype)})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func readPrettyOSRelease(path string) string {
|
||||
if b, err := os.ReadFile(path); err == nil {
|
||||
for _, ln := range bytes.Split(b, []byte{'\n'}) {
|
||||
if bytes.HasPrefix(ln, []byte("PRETTY_NAME=")) {
|
||||
val := strings.Trim(string(bytes.TrimPrefix(ln, []byte("PRETTY_NAME="))), `"`)
|
||||
return strings.TrimSpace(val)
|
||||
}
|
||||
}
|
||||
|
||||
name := parseKey(bytesToLower(b), "name=")
|
||||
version := parseKey(bytesToLower(b), "version=")
|
||||
if name != "" && version != "" {
|
||||
return strings.TrimSpace(name + " " + version)
|
||||
}
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseKey(b []byte, key string) string {
|
||||
for _, ln := range bytes.Split(b, []byte{'\n'}) {
|
||||
if bytes.HasPrefix(ln, []byte(key)) {
|
||||
val := bytes.TrimPrefix(ln, []byte(key))
|
||||
val = bytes.Trim(val, `"' `)
|
||||
return string(val)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func bytesToLower(b []byte) []byte { return bytes.ToLower(b) }
|
||||
|
||||
func getUname() string {
|
||||
var u syscall.Utsname
|
||||
if err := syscall.Uname(&u); err == nil {
|
||||
return fromUts(u.Release[:])
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func fromUts(a []int8) string {
|
||||
var b []byte
|
||||
for _, c := range a {
|
||||
if c == 0 {
|
||||
break
|
||||
}
|
||||
b = append(b, byte(c))
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func detectCPU() (model string, hz uint64) {
|
||||
|
||||
kPaths := []string{
|
||||
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
|
||||
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq",
|
||||
}
|
||||
for _, p := range kPaths {
|
||||
if b, err := os.ReadFile(p); err == nil {
|
||||
|
||||
if n, err := strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64); err == nil && n > 0 {
|
||||
hz = n * 1000
|
||||
|
||||
model = readCPUModelFromProc()
|
||||
if model == "" {
|
||||
model = "unknown"
|
||||
}
|
||||
return model, hz
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b, err := os.ReadFile("/proc/cpuinfo"); err == nil {
|
||||
lower := bytes.ToLower(b)
|
||||
|
||||
if m := findFirstField(lower, "model name"); m != "" {
|
||||
model = trimField(m)
|
||||
} else if m := findFirstField(lower, "hardware"); m != "" {
|
||||
model = trimField(m)
|
||||
} else if m := findFirstField(lower, "processor"); m != "" {
|
||||
model = trimField(m)
|
||||
} else if m := findFirstField(lower, "model"); m != "" {
|
||||
|
||||
model = trimField(m)
|
||||
} else {
|
||||
model = "unknown"
|
||||
}
|
||||
|
||||
if mhz := findFirstField(lower, "cpu mhz"); mhz != "" {
|
||||
if f, err := strconv.ParseFloat(strings.TrimSpace(mhz), 64); err == nil && f > 0 {
|
||||
hz = uint64(f * 1e6)
|
||||
}
|
||||
}
|
||||
|
||||
return model, hz
|
||||
}
|
||||
|
||||
return "unknown", 0
|
||||
}
|
||||
|
||||
func readCPUModelFromProc() string {
|
||||
if b, err := os.ReadFile("/proc/cpuinfo"); err == nil {
|
||||
lower := bytes.ToLower(b)
|
||||
if m := findFirstField(lower, "model name"); m != "" {
|
||||
return trimField(m)
|
||||
}
|
||||
if m := findFirstField(lower, "hardware"); m != "" {
|
||||
return trimField(m)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findFirstField(b []byte, key string) string {
|
||||
|
||||
t := []byte(key + ":")
|
||||
if i := bytes.Index(b, t); i != -1 {
|
||||
|
||||
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
|
||||
line := b[i : i+j]
|
||||
parts := bytes.SplitN(line, []byte{':'}, 2)
|
||||
if len(parts) == 2 {
|
||||
return string(parts[1])
|
||||
}
|
||||
} else {
|
||||
parts := bytes.SplitN(b[i:], []byte{':'}, 2)
|
||||
if len(parts) == 2 {
|
||||
return string(parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t2 := []byte(key + "\t:")
|
||||
if i := bytes.Index(b, t2); i != -1 {
|
||||
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
|
||||
line := b[i : i+j]
|
||||
parts := bytes.SplitN(line, []byte{':'}, 2)
|
||||
if len(parts) == 2 {
|
||||
return string(parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t3 := []byte(key + "=")
|
||||
if i := bytes.Index(b, t3); i != -1 {
|
||||
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
|
||||
line := b[i : i+j]
|
||||
parts := bytes.SplitN(line, []byte{'='}, 2)
|
||||
if len(parts) == 2 {
|
||||
return string(parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func trimField(s string) string {
|
||||
return strings.TrimSpace(strings.Trim(s, `"`))
|
||||
}
|
||||
|
||||
func parseMemInfo() (memTotal, memAvailable, swapTotal, swapFree uint64) {
|
||||
if b, err := os.ReadFile("/proc/meminfo"); err == nil {
|
||||
for _, ln := range bytes.Split(b, []byte{'\n'}) {
|
||||
if len(ln) == 0 {
|
||||
continue
|
||||
}
|
||||
l := bytes.ToLower(ln)
|
||||
fields := bytes.Fields(l)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := string(fields[0])
|
||||
valStr := string(fields[1])
|
||||
n, err := strconv.ParseUint(valStr, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "memtotal:":
|
||||
memTotal = n * 1024
|
||||
case "memavailable:":
|
||||
memAvailable = n * 1024
|
||||
case "swaptotal:":
|
||||
swapTotal = n * 1024
|
||||
case "swapfree:":
|
||||
swapFree = n * 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getStorageAndType(path string) (used, total uint64, fstype string) {
|
||||
var st syscall.Statfs_t
|
||||
if syscall.Statfs(path, &st) == nil {
|
||||
total = st.Blocks * uint64(st.Bsize)
|
||||
avail := st.Bavail * uint64(st.Bsize)
|
||||
used = total - avail
|
||||
}
|
||||
|
||||
if b, err := os.ReadFile("/proc/mounts"); err == nil {
|
||||
lines := bytes.Split(b, []byte{'\n'})
|
||||
|
||||
bestLen := -1
|
||||
for _, ln := range lines {
|
||||
if len(ln) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := bytes.Fields(ln)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
mp := string(parts[1])
|
||||
if strings.HasPrefix(path, mp) && len(mp) > bestLen {
|
||||
bestLen = len(mp)
|
||||
fstype = string(parts[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
if fstype == "" {
|
||||
fstype = "fs"
|
||||
}
|
||||
return used, total, strings.ToLower(fstype)
|
||||
}
|
||||
|
||||
func detectDesktop() string {
|
||||
|
||||
envs := []string{
|
||||
"XDG_CURRENT_DESKTOP",
|
||||
"DESKTOP_SESSION",
|
||||
"XDG_SESSION_DESKTOP",
|
||||
"GDMSESSION",
|
||||
"SWAYSOCK",
|
||||
}
|
||||
for _, e := range envs {
|
||||
if v := os.Getenv(e); v != "" {
|
||||
|
||||
return strings.ToLower(strings.Split(v, ":")[0])
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||
return "wayland"
|
||||
}
|
||||
if os.Getenv("DISPLAY") != "" {
|
||||
return "x11"
|
||||
}
|
||||
|
||||
return "tty"
|
||||
}
|
||||
|
||||
func printAligned(values, keys []string, cube []string) {
|
||||
n := len(values)
|
||||
cubeH := len(cube)
|
||||
|
||||
valW := 0
|
||||
keyW := 0
|
||||
for _, v := range values {
|
||||
if visibleLen(v) > valW {
|
||||
valW = visibleLen(v)
|
||||
}
|
||||
}
|
||||
for _, k := range keys {
|
||||
if visibleLen(k) > keyW {
|
||||
keyW = visibleLen(k)
|
||||
}
|
||||
}
|
||||
|
||||
cubeCol := valW + 2 + 3 + keyW + 2
|
||||
|
||||
lines := n
|
||||
if cubeH > lines {
|
||||
lines = cubeH
|
||||
}
|
||||
cubeStart := 0
|
||||
if n > cubeH {
|
||||
cubeStart = (n - cubeH) / 2
|
||||
}
|
||||
|
||||
for i := 0; i < lines; i++ {
|
||||
left := ""
|
||||
mid := ""
|
||||
if i < n {
|
||||
left = values[i]
|
||||
mid = "<- " + bold(keys[i])
|
||||
}
|
||||
line := ""
|
||||
if left != "" || mid != "" {
|
||||
|
||||
if left != "" {
|
||||
line += padRight(left, valW)
|
||||
} else {
|
||||
line += strings.Repeat(" ", valW)
|
||||
}
|
||||
line += " "
|
||||
|
||||
if mid != "" {
|
||||
|
||||
key := keys[i]
|
||||
paddedKey := padRight(key, keyW)
|
||||
mid = "<- " + bold(paddedKey)
|
||||
line += mid
|
||||
}
|
||||
}
|
||||
|
||||
vis := visibleLen(line)
|
||||
if pad := cubeCol - vis; pad > 0 {
|
||||
line += strings.Repeat(" ", pad)
|
||||
}
|
||||
|
||||
cIdx := i - cubeStart
|
||||
if cIdx >= 0 && cIdx < cubeH {
|
||||
line += cube[cIdx]
|
||||
}
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
|
||||
func bold(s string) string { return "\033[1m" + s + "\033[0m" }
|
||||
|
||||
func padRight(s string, w int) string {
|
||||
if visibleLen(s) >= w {
|
||||
return s
|
||||
}
|
||||
return s + strings.Repeat(" ", w-visibleLen(s))
|
||||
}
|
||||
|
||||
func visibleLen(s string) int {
|
||||
n := 0
|
||||
esc := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '\033' {
|
||||
esc = true
|
||||
continue
|
||||
}
|
||||
if esc {
|
||||
if c == 'm' {
|
||||
esc = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func hr(b uint64) string {
|
||||
const (
|
||||
KB = 1024
|
||||
MB = KB * 1024
|
||||
GB = MB * 1024
|
||||
)
|
||||
if b >= GB {
|
||||
f := float64(b) / float64(GB)
|
||||
|
||||
return fmt.Sprintf("%.2f gb", f)
|
||||
}
|
||||
if b >= MB {
|
||||
return fmt.Sprintf("%.0f mb", float64(b)/float64(MB))
|
||||
}
|
||||
return fmt.Sprintf("%d kb", b/KB)
|
||||
}
|
||||
Reference in New Issue
Block a user