goGoFetch/src/providers/systemstats.go
nekohepott 75e97a09f6
fix: handle error in Scanner
костыль тот ещё
2026-06-25 19:00:28 +03:00

312 lines
6.8 KiB
Go

package providers
import (
"bufio"
"fmt"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
"syscall"
"time"
)
var id, prettyName string
const gpuCacheFile = "/tmp/gogofetch_gpu_cache"
func GetDist() (string, string, error) {
f, _ := os.Open("/etc/os-release")
defer func(f *os.File) {
err := f.Close()
if err != nil {
fmt.Println("Error closing file:", err)
}
}(f)
s := bufio.NewScanner(f)
for s.Scan() {
t := s.Text()
if strings.HasPrefix(t, "ID=") {
id = strings.TrimPrefix(t, "ID=")
id = strings.Trim(id, "\"")
id = strings.ReplaceAll(id, "'", "")
}
if strings.HasPrefix(t, "PRETTY_NAME=") {
prettyName = strings.TrimPrefix(t, "PRETTY_NAME=")
prettyName = strings.Trim(prettyName, "\"")
prettyName = strings.ReplaceAll(prettyName, "'", "")
}
}
if err := s.Err(); err != nil {
return "", "", fmt.Errorf("reading input: %w", err)
}
if id == "" && prettyName == "" {
return "unknown", "unknown", nil
}
return id, prettyName, nil
}
func getSizeRam(total, avaible, used *int) string {
if *total >= 1024*1024 && *avaible >= 1024*1024 {
*used = *total - *avaible
usedGiB := float64(*used) / (1024 * 1024)
totalGiB := float64(*total) / (1024 * 1024)
ram := fmt.Sprintf("%.2f / %.2f GiB", usedGiB, totalGiB)
ram = strings.TrimSpace(ram)
return ram
}
totalMB := *total / 1024
avaibleMB := *avaible / 1024
*used = totalMB - avaibleMB
return fmt.Sprintf("%d / %d MiB", *used, totalMB)
}
func GetRam() (string, error) {
f, err := os.Open("/proc/meminfo")
if err != nil {
return "", err
}
defer f.Close()
var total, available, used int
s := bufio.NewScanner(f)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "MemTotal:") {
_, err2 := fmt.Sscanf(line, "MemTotal: %d kB", &total)
if err2 != nil {
return "", err
}
}
if strings.HasPrefix(line, "MemAvailable:") {
_, err := fmt.Sscanf(line, "MemAvailable: %d kB", &available)
if err != nil {
return "", err
}
}
}
if err := s.Err(); err != nil {
return "", fmt.Errorf("reading input: %w", err)
}
ram := getSizeRam(&total, &available, &used)
ram = strings.TrimSpace(ram)
return ram, nil
}
func GetCpu() (string, error) {
f, err := os.Open("/proc/cpuinfo")
if err != nil {
return "unknown", err
}
defer f.Close()
var cpu string
s := bufio.NewScanner(f)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "model name") {
parts := strings.SplitN(line, ":", 2)
if len(parts) > 1 {
cpu := strings.TrimSpace(parts[1])
return cpu, nil
}
}
}
if err := s.Err(); err != nil {
return "", fmt.Errorf("reading input: %w", err)
}
return strings.TrimSpace(cpu), nil
}
func readGpuCache() (string, bool) {
data, err := os.ReadFile(gpuCacheFile)
if err != nil {
return "", false
}
return strings.TrimSpace(string(data)), true
}
func writeGpuCache(gpuData string) {
_ = os.WriteFile(gpuCacheFile, []byte(gpuData), 0644)
}
func GetGpu() string {
if cached, ok := readGpuCache(); ok && cached != "" {
return cached
}
out, err := exec.Command("sh", "-c", `lspci -mm | awk -F'"' '$2=="VGA compatible controller" || $2=="3D controller" || $2=="Display controller"'`).Output()
if err != nil || len(out) == 0 {
result := "unknown GPU"
writeGpuCache(result)
return result
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
var gpus []string
for _, line := range lines {
re := regexp.MustCompile(`"([^"]+)"`)
matches := re.FindAllStringSubmatch(line, -1)
if len(matches) >= 3 {
vendor := matches[1][1]
device := matches[2][1]
cleaned := cleanGpuString(vendor + " " + device)
gpus = append(gpus, cleaned)
}
}
var result string
if len(gpus) > 0 {
result = strings.Join(gpus, " / ")
} else {
result = "GPU not found"
}
writeGpuCache(result)
return result
}
func cleanGpuString(raw string) string {
prettyNameRe := regexp.MustCompile(`\[(.*?)]`)
match := prettyNameRe.FindStringSubmatch(raw)
res := raw
if len(match) > 1 {
vendor := ""
if strings.Contains(strings.ToLower(raw), "nvidia") {
vendor = "NVIDIA "
}
if strings.Contains(strings.ToLower(raw), "intel") {
vendor = "Intel "
}
if strings.Contains(strings.ToLower(raw), "amd") {
vendor = "AMD "
}
res = vendor + match[1]
}
re := regexp.MustCompile(`\(.*?\)|\[.*?]`)
res = re.ReplaceAllString(res, "")
fluff := []string{"Corporation", "Controller", "VGA compatible", "3D", "Graphics", "Device"}
for _, word := range fluff {
res = strings.ReplaceAll(res, word, "")
}
return strings.Join(strings.Fields(res), " ")
}
func GetKernel() string {
out, err := os.ReadFile("/proc/version")
if err != nil {
return "unknown"
}
str := strings.TrimSpace(string(out))
str = strings.ReplaceAll(str, " version ", " ")
if idx := strings.Index(str, "("); idx != -1 {
str = str[:idx]
}
return strings.TrimSpace(str)
}
func GetHostname() string {
hostname, err := os.ReadFile("/proc/sys/kernel/hostname")
if err != nil {
return "unknown"
}
username := os.Getenv("USER")
if username == "" {
username = os.Getenv("LOGNAME")
}
if username == "" {
return "unknown"
}
return fmt.Sprintf("%s@%s", username, strings.TrimSpace(string(hostname)))
}
func GetTerminal() string {
shellPID := os.Getppid()
out, err := exec.Command("ps", "-p", fmt.Sprint(shellPID), "-o", "ppid=").Output()
if err != nil {
return os.Getenv("TERM")
}
terminalPID := strings.TrimSpace(string(out))
nameOut, err := exec.Command("ps", "-p", terminalPID, "-o", "comm=").Output()
if err != nil {
return os.Getenv("TERM")
}
// those subprocess ARE NOT SLOWING DOWN THE FETCH MELVI, execution time is the same, you can check it
return strings.TrimSpace(string(nameOut))
}
func GetDE() string {
de := os.Getenv("XDG_CURRENT_DESKTOP")
if de == "" {
return "unknown or w/o GUI"
}
return strings.TrimSpace(de)
}
func GetUptime() string {
data, err := os.ReadFile("/proc/uptime")
if err != nil {
return "0"
}
uptimeStr := strings.Fields(string(data))[0]
seconds, err := strconv.ParseFloat(uptimeStr, 64)
if err != nil {
_, err := fmt.Fprintf(os.Stderr, "Error: %v\n", err)
if err != nil {
return "error printing uptime"
}
return ""
}
d := time.Duration(seconds) * time.Second
formatted := d.Round(time.Second).String()
spaced := strings.NewReplacer("h", "h ", "m", "m ", "s", "s ").Replace(formatted)
return spaced
}
func GetShell() string {
out, err := exec.Command("ps", "-p", strconv.Itoa(os.Getppid()), "-o", "comm=").Output()
if err != nil {
return path.Base(os.Getenv("SHELL"))
}
return strings.TrimSpace(string(out))
}
func GetAge() string {
info, err := os.Stat("/etc/machine-id")
if err != nil {
info, err = os.Stat("/")
if err != nil {
return "unknown"
}
}
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return "unknown"
}
birthTime := time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec)
days := int(time.Since(birthTime).Hours() / 24)
return fmt.Sprintf("%d days", days)
}