summaryrefslogtreecommitdiff
path: root/internal/app
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-13 22:52:46 +0200
committerPaul Buetow <paul@buetow.org>2026-02-13 22:52:46 +0200
commitcd5a3614baab756a41d764b79308afeea93f12dd (patch)
treeefc8c31e8b162ca2121ba92c841322119e6d3b04 /internal/app
parentbf7c6ade292a6444877797c8d699d147aceb57cc (diff)
Remove Perl version and build files; add .gitignore for .serena/
Amp-Thread-ID: https://ampcode.com/threads/T-019c58b3-06fb-733d-8fc1-f268fe7f70d5 Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/app.go35
-rw-r--r--internal/app/script.go29
-rw-r--r--internal/app/store.go103
3 files changed, 167 insertions, 0 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
new file mode 100644
index 0000000..946e985
--- /dev/null
+++ b/internal/app/app.go
@@ -0,0 +1,35 @@
+package app
+
+import (
+ "context"
+ "sync"
+
+ "github.com/loadbars/loadbars/internal/collector"
+ "github.com/loadbars/loadbars/internal/config"
+ "github.com/loadbars/loadbars/internal/display"
+)
+
+// Run starts the loadbars application: collectors and display.
+// It blocks until the user quits (e.g. 'q' key).
+func Run(cfg *config.Config) error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ scriptPath := ScriptPath()
+ store := NewStore()
+
+ var wg sync.WaitGroup
+ for _, host := range cfg.Hosts {
+ h := host
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _ = collector.Run(ctx, h, cfg, store, scriptPath)
+ }()
+ }
+
+ err := display.Run(ctx, cfg, store)
+ cancel()
+ wg.Wait()
+ return err
+}
diff --git a/internal/app/script.go b/internal/app/script.go
new file mode 100644
index 0000000..2701cb2
--- /dev/null
+++ b/internal/app/script.go
@@ -0,0 +1,29 @@
+package app
+
+import (
+ "os"
+ "path/filepath"
+)
+
+// ScriptPath returns the path to the loadbars-remote.sh script.
+// It looks for scripts/loadbars-remote.sh relative to the executable, then current dir.
+func ScriptPath() string {
+ exe, err := os.Executable()
+ if err == nil {
+ dir := filepath.Dir(exe)
+ // When installed: exe is /usr/bin/loadbars, script might be /usr/share/loadbars/scripts/
+ for _, sub := range []string{"scripts/loadbars-remote.sh", "../scripts/loadbars-remote.sh", "../../scripts/loadbars-remote.sh"} {
+ p := filepath.Join(dir, sub)
+ if _, err := os.Stat(p); err == nil {
+ return p
+ }
+ }
+ }
+ // Development: run from repo root
+ if p, err := filepath.Abs("scripts/loadbars-remote.sh"); err == nil {
+ if _, err := os.Stat(p); err == nil {
+ return p
+ }
+ }
+ return "scripts/loadbars-remote.sh"
+}
diff --git a/internal/app/store.go b/internal/app/store.go
new file mode 100644
index 0000000..f9c90ef
--- /dev/null
+++ b/internal/app/store.go
@@ -0,0 +1,103 @@
+package app
+
+import (
+ "sync"
+
+ "github.com/loadbars/loadbars/internal/collector"
+ "github.com/loadbars/loadbars/internal/stats"
+)
+
+// Store holds current stats from all hosts and implements collector.StatsStore.
+type Store struct {
+ mu sync.RWMutex
+ // host -> *hostData
+ hosts map[string]*hostData
+}
+
+type hostData struct {
+ load1, load5, load15 string
+ mem map[string]int64
+ net map[string]stats.NetStamp
+ cpu map[string]collector.CPULine
+}
+
+// NewStore creates an empty store.
+func NewStore() *Store {
+ return &Store{hosts: make(map[string]*hostData)}
+}
+
+func (s *Store) getOrCreate(host string) *hostData {
+ if s.hosts[host] == nil {
+ s.hosts[host] = &hostData{
+ mem: make(map[string]int64),
+ net: make(map[string]stats.NetStamp),
+ cpu: make(map[string]collector.CPULine),
+ }
+ }
+ return s.hosts[host]
+}
+
+// SetLoadAvg implements collector.StatsStore.
+func (s *Store) SetLoadAvg(host, load1, load5, load15 string) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ d := s.getOrCreate(host)
+ d.load1, d.load5, d.load15 = load1, load5, load15
+}
+
+// SetCPU implements collector.StatsStore.
+func (s *Store) SetCPU(host, name string, line collector.CPULine) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ d := s.getOrCreate(host)
+ d.cpu[name] = line
+}
+
+// SetMem implements collector.StatsStore.
+func (s *Store) SetMem(host, key string, value int64) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ d := s.getOrCreate(host)
+ d.mem[key] = value
+}
+
+// SetNet implements collector.StatsStore.
+func (s *Store) SetNet(host, iface string, net collector.NetLine, stamp float64) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ d := s.getOrCreate(host)
+ d.net[iface] = stats.NetStamp{B: net.B, Tb: net.Tb, Stamp: stamp}
+}
+
+// Snapshot returns a copy of current stats for all hosts (for display).
+func (s *Store) Snapshot() map[string]*stats.HostStats {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ out := make(map[string]*stats.HostStats, len(s.hosts))
+ for h, d := range s.hosts {
+ mem := make(map[string]int64, len(d.mem))
+ for k, v := range d.mem {
+ mem[k] = v
+ }
+ net := make(map[string]stats.NetStamp, len(d.net))
+ for k, v := range d.net {
+ net[k] = v
+ }
+ cpu := make(map[string]collector.CPULine, len(d.cpu))
+ for k, v := range d.cpu {
+ cpu[k] = v
+ }
+ out[h] = &stats.HostStats{
+ LoadAvg1: d.load1,
+ LoadAvg5: d.load5,
+ LoadAvg15: d.load15,
+ Mem: mem,
+ Net: net,
+ CPU: cpu,
+ }
+ }
+ return out
+}
+
+var _ stats.Source = (*Store)(nil)
+var _ collector.StatsStore = (*Store)(nil)