commit d962c87fe39c83c58bccceca60fce74f3b8d6c66 Author: M.Schirmer Date: Tue Sep 9 20:01:34 2025 +0200 initial commit with AI Code diff --git a/cmd/monitor/main.go b/cmd/monitor/main.go new file mode 100644 index 0000000..7a58f27 --- /dev/null +++ b/cmd/monitor/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/mem" +) + +func isSupported() bool { + goos := os.Getenv("TMUX_SYSUSAGE_GOOS") // Debug/Override optional + if goos == "" { + goos = detectGOOS() + } + switch goos { + case "darwin": + return true + case "linux": + id := linuxID() + return id == "ubuntu" || id == "arch" + default: + return false + } +} + +func detectGOOS() string { + // runtime.GOOS vermeiden, um Import minimal zu halten? Es ist Standard—nutzen wir. + // (Minimale Imports sind nice-to-have, aber runtime ist winzig.) + // Wir nehmen ihn doch, weil zuverlässig. + // → Ein einzelner Import mehr ist ok. + return runtimeGOOS() +} + +//go:noinline +func runtimeGOOS() string { + // kleiner Trick: getrennt, damit der Compiler runtime nicht inlined—irrelevant, aber clean. + return getGOOS() +} + +//go:linkname getGOOS runtime.GOOS +func getGOOS() string + +func linuxID() string { + f, err := os.Open("/etc/os-release") + if err != nil { + return "" + } + defer f.Close() + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + if strings.HasPrefix(line, "ID=") { + val := strings.TrimPrefix(line, "ID=") + val = strings.Trim(val, `"`) + return strings.ToLower(val) + } + } + return "" +} + +func main() { + if !isSupported() { + // Keine Ausgabe auf nicht unterstützten Systemen + return + } + // Kurze CPU-Stichprobe + values, err := cpu.Percent(400*time.Millisecond, false) + if err != nil || len(values) == 0 { + return + } + vm, err := mem.VirtualMemory() + if err != nil { + return + } + // Kompakte tmux-Ausgabe + fmt.Printf("CPU %.0f%% | RAM %.0f%%", values[0], vm.UsedPercent) +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b63f094 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module gitea.mschirmer.com/m13r/cpu-mem-monitor + +go 1.25.1 + +require github.com/shirou/gopsutil/v3 v3.24.5 + +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61e6f38 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9a25492 --- /dev/null +++ b/main.go @@ -0,0 +1,111 @@ +// cmd/sysusage/main.go +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "time" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/mem" +) + +type Result struct { + CPUPercent float64 `json:"cpu_percent"` + RAMPercent float64 `json:"ram_percent"` +} + +func sampleCPU(interval time.Duration, samples int) (float64, error) { + if samples <= 0 { + samples = 1 + } + var sum float64 + for i := 0; i < samples; i++ { + // cpu.Percent blockiert für "interval" und liefert die Auslastung in diesem Zeitfenster + values, err := cpu.Percent(interval, false) + if err != nil { + return 0, err + } + if len(values) == 0 { + return 0, fmt.Errorf("no cpu values returned") + } + sum += values[0] + } + return sum / float64(samples), nil +} + +func readRAM() (float64, error) { + vm, err := mem.VirtualMemory() + if err != nil { + return 0, err + } + return vm.UsedPercent, nil +} + +func main() { + var ( + intervalStr string + samples int + jsonOut bool + watch bool + ) + + flag.StringVar(&intervalStr, "interval", "1s", "Messintervall pro Stichprobe (z.B. 200ms, 1s, 2s)") + flag.IntVar(&samples, "samples", 1, "Anzahl der Stichproben (über das Intervall gemittelt)") + flag.BoolVar(&jsonOut, "json", false, "Ausgabe als JSON") + flag.BoolVar(&watch, "watch", false, "Kontinuierlich messen, bis Strg+C") + flag.Parse() + + interval, err := time.ParseDuration(intervalStr) + if err != nil || interval <= 0 { + log.Fatalf("ungültiges -interval: %v", intervalStr) + } + + printOnce := func() error { + cpuPct, err := sampleCPU(interval, samples) + if err != nil { + return fmt.Errorf("CPU-Messung fehlgeschlagen: %w", err) + } + ramPct, err := readRAM() + if err != nil { + return fmt.Errorf("RAM-Messung fehlgeschlagen: %w", err) + } + + if jsonOut { + out := Result{CPUPercent: cpuPct, RAMPercent: ramPct} + enc := json.NewEncoder(stdoutNoHTMLEscape{}) + enc.SetIndent("", " ") + return enc.Encode(out) + } + + fmt.Printf("CPU: %.1f%% | RAM: %.1f%%\n", cpuPct, ramPct) + return nil + } + + if watch { + for { + if err := printOnce(); err != nil { + log.Fatal(err) + } + // Bei watch: zwischen den Zeilen kurz pausieren, damit die Ausgabe lesbar bleibt. + time.Sleep(250 * time.Millisecond) + } + } else { + if err := printOnce(); err != nil { + log.Fatal(err) + } + } +} + +// stdoutNoHTMLEscape verhindert das HTML-Escaping des Standard-Encoders (nur Kosmetik für JSON) +type stdoutNoHTMLEscape struct{} + +func (stdoutNoHTMLEscape) Write(p []byte) (int, error) { return fmt.Print(string(p)) } +func (stdoutNoHTMLEscape) Encode(v any) error { + enc := json.NewEncoder(stdoutNoHTMLEscape{}) + enc.SetEscapeHTML(false) + return enc.Encode(v) +} + diff --git a/monitor b/monitor new file mode 100755 index 0000000..a1c8845 Binary files /dev/null and b/monitor differ diff --git a/plugin.tmux b/plugin.tmux new file mode 100644 index 0000000..07c3894 --- /dev/null +++ b/plugin.tmux @@ -0,0 +1,12 @@ +# Binary bauen (still). Wenn go fehlt: überspringen +run-shell "cd '#{plugin_path}' && GO111MODULE=on go mod tidy >/dev/null 2>&1 || true" +run-shell "cd '#{plugin_path}' && GO111MODULE=on go build -ldflags='-s -w' -o bin.sysusage ./cmd/monitor >/dev/null 2>&1 || true" + +# tmux-Variable setzen: #{sysusage} +set -g @sysusage_cmd '#{plugin_path}/bin.sysusage' +set -g status-interval 2 + +# Du kannst später im Theme einfach schreiben: +# set -g status-right "... #{sysusage} ..." +set -g -F @sysusage '#(@sysusage_cmd)' +