diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-13 23:06:30 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-13 23:06:30 +0200 |
| commit | 8ce6aa0a181e123f156ef3d1b75d939f56757ac6 (patch) | |
| tree | 4e89b84833f32c17ae8a8a2e1921d5b4967ecc66 | |
| parent | 39fcaa7a69947ee45d445e3aab7b49ce868b9d9c (diff) | |
Fix core toggle off and add memory bars (key 2)
- Clear renderer every frame so toggling cores off shows single bar again
- Add drawMemBar: RAM (dark grey/black) and Swap (grey/black) per host
- Remove redrawBg; always clear before draw
- README updates
Co-authored-by: Cursor <cursoragent@cursor.com>
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | internal/display/display.go | 79 |
2 files changed, 71 insertions, 24 deletions
@@ -36,7 +36,7 @@ loadbars servername{01..50}.example.com --showcores 1 ## Description -Loadbars is a small script that can be used to observe CPU loads of several remote servers at once in real time. It connects with SSH (using SSH public/private key auth) to several servers at once and vizualizes all server CPUs and memory statistics right next each other (either summarized or each core separately). Loadbars is not a tool for collecting CPU loads and drawing graphs for later analysis. However, since such tools require a significant amount of time before producing results, Loadbars lets you observe the current state immediately. Loadbars does not remember or record any load information. It just shows the current CPU usages like top or vmstat does. +Loadbars is a tool that can be used to observe CPU loads of several remote servers at once in real time. It connects with SSH (using SSH public/private key auth) to several servers at once and vizualizes all server CPUs and memory statistics right next each other (either summarized or each core separately). Loadbars is not a tool for collecting CPU loads and drawing graphs for later analysis. However, since such tools require a significant amount of time before producing results, Loadbars lets you observe the current state immediately. Loadbars does not remember or record any load information. It just shows the current CPU usages like top or vmstat does. ## Build and run @@ -49,24 +49,22 @@ go build -o loadbars ./cmd/loadbars Or use [mage](https://magefile.org): `mage build` (default), `mage test`, `mage install` (set `DESTDIR` for install path), `mage uninstall` / `mage deinstall`. -Remote hosts need no Perl: the binary pipes `scripts/loadbars-remote.sh` over SSH. +Remote hosts need no Go: the binary pipes `scripts/loadbars-remote.sh` over SSH. ## Installation ### Dependencies (Fedora/RHEL/CentOS) -To run loadbars on Fedora Linux, you need to install the following packages: +To run loadbars on Fedora Linux, you need the install the SDL2 development libraries: ```bash -sudo dnf install perl perl-SDL perl-Alien-SDL perl-Proc-ProcessTable +sudo dnf install SDL2-devel ``` -### Dependencies for Remote Hosts - -For monitoring remote servers via SSH, the remote hosts need: +On Ubuntu/Debian: ```bash -sudo dnf install perl-Time-HiRes +sudo apt install libsdl2-dev ``` ### Running from Source @@ -138,4 +136,4 @@ The Go build of loadbars links to **go-sdl2** (github.com/veandco/go-sdl2), whic ## Author -Paul Buetow - <http://buetow.org> +Paul Buetow - <http://paul.buetow.org> diff --git a/internal/display/display.go b/internal/display/display.go index fede729..49f9f10 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -53,7 +53,6 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { showNet := cfg.ShowNet extended := cfg.Extended winW, winH := int32(width), int32(height) - redrawBg := true // Previous CPU state for delta (key = host;cpuName) prevCPU := make(map[string]collector.CPULine) @@ -86,19 +85,15 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { return nil case sdl.K_1: showCores = !showCores - redrawBg = true fmt.Println("==> Toggled show cores:", showCores) case sdl.K_2: showMem = !showMem - redrawBg = true fmt.Println("==> Toggled show mem:", showMem) case sdl.K_3: showNet = !showNet - redrawBg = true fmt.Println("==> Toggled show net:", showNet) case sdl.K_e: extended = !extended - redrawBg = true case sdl.K_h: printHotkeys() case sdl.K_w: @@ -117,30 +112,25 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { winW = 1 } window.SetSize(winW, winH) - redrawBg = true case sdl.K_RIGHT: winW += 100 if winW > int32(cfg.MaxWidth) { winW = int32(cfg.MaxWidth) } window.SetSize(winW, winH) - redrawBg = true case sdl.K_UP: winH -= 100 if winH < 1 { winH = 1 } window.SetSize(winW, winH) - redrawBg = true case sdl.K_DOWN: winH += 100 window.SetSize(winW, winH) - redrawBg = true } case *sdl.WindowEvent: if ev.Event == sdl.WINDOWEVENT_RESIZED { winW, winH = ev.Data1, ev.Data2 - redrawBg = true } } } @@ -168,11 +158,9 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { barWidth = 1 } - if redrawBg { - renderer.SetDrawColor(0, 0, 0, 255) - renderer.Clear() - redrawBg = false - } + // Clear every frame so toggling cores off (or changing bar count) doesn't leave stale bars + renderer.SetDrawColor(0, 0, 0, 255) + renderer.Clear() x := int32(0) hosts := sortedHosts(snap) @@ -187,6 +175,10 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error { drawCPUBar(renderer, h.CPU[name], prevCPU[host+";"+name], barWidth, &x, winH) prevCPU[host+";"+name] = h.CPU[name] } + // Draw memory bar(s) for this host when showMem + if showMem { + drawMemBar(renderer, h, barWidth, &x, winH) + } } renderer.Present() @@ -293,6 +285,63 @@ func drawCPUBar(renderer *sdl.Renderer, cur, prev collector.CPULine, barW int32, fill(constants.Red.R, constants.Red.G, constants.Red.B, stealPct) } +// drawMemBar draws one memory bar (RAM left half, Swap right half) for a host. +func drawMemBar(renderer *sdl.Renderer, h *stats.HostStats, barW int32, x *int32, winH int32) { + defer func() { *x += barW + 1 }() + if h.Mem == nil { + return + } + memTotal := h.Mem["MemTotal"] + memFree := h.Mem["MemFree"] + swapTotal := h.Mem["SwapTotal"] + swapFree := h.Mem["SwapFree"] + + halfW := barW / 2 + barH := float64(winH) / 100.0 + + // RAM: used (dark grey) from bottom, free (black) on top + if memTotal > 0 { + ramUsedPct := 100 - int(100*memFree/memTotal) + if ramUsedPct < 0 { + ramUsedPct = 0 + } + if ramUsedPct > 100 { + ramUsedPct = 100 + } + ramUsedH := int32(float64(ramUsedPct) * barH) + if ramUsedH > 0 { + renderer.SetDrawColor(constants.DarkGrey.R, constants.DarkGrey.G, constants.DarkGrey.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: winH - ramUsedH, W: halfW, H: ramUsedH}) + } + ramFreeH := winH - ramUsedH + if ramFreeH > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x, Y: 0, W: halfW, H: ramFreeH}) + } + } + + // Swap: used (grey) from bottom, free (black) on top + if swapTotal > 0 { + swapUsedPct := 100 - int(100*swapFree/swapTotal) + if swapUsedPct < 0 { + swapUsedPct = 0 + } + if swapUsedPct > 100 { + swapUsedPct = 100 + } + swapUsedH := int32(float64(swapUsedPct) * barH) + if swapUsedH > 0 { + renderer.SetDrawColor(constants.Grey.R, constants.Grey.G, constants.Grey.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: winH - swapUsedH, W: halfW, H: swapUsedH}) + } + swapFreeH := winH - swapUsedH + if swapFreeH > 0 { + renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255) + renderer.FillRect(&sdl.Rect{X: *x + halfW, Y: 0, W: halfW, H: swapFreeH}) + } + } +} + func printHotkeys() { fmt.Println("=> Hotkeys: 1=cores 2=mem 3=net e=extended h=help n=next net q=quit w=write config a/y=cpu avg d/c=net avg f/v=link scale arrows=resize") } |
