summaryrefslogtreecommitdiff
path: root/internal/collector
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-18 10:10:39 +0200
committerPaul Buetow <paul@buetow.org>2026-02-18 10:10:39 +0200
commit69f5017434298f1ffd4cdc30c30b95d0f4bd344f (patch)
treec41a378bc8c4c7338f392cde7a4185658d4dfb12 /internal/collector
parentf1951f2ee1e83d802030c257d4a1df099ec08976 (diff)
refactor: enforce Go best practices (function size, ordering, formatting)
- config: split set() (62L) into setSizeAndTuning() + setDisplayFlags() - collector: split Run() (70L) into Run + startLocalScanner + startRemoteScanner + parseCollectorStream + dispatchCollectorLine; each <30L - collector: remove unused script_embed.go (RemoteScript was dead code) - display: move newRunState constructor before Run() per constructor-first rule - display: replace loadPeak IIFE with a plain initLoadPeak variable - display: split handleKey() (114L) into handleToggleKeys + handleAdjustAndSave + handleResizeKeys; add nil guard in handleResizeKeys for test safety - display: split drawNetBarSmoothed() (75L) into drawNetBarSmoothed + smoothNetUtilization + drawNetHalves; each <30L - all: gofmt -w to fix formatting drift in display and config files All tests pass (go test ./...). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/collector')
-rw-r--r--internal/collector/collector.go152
-rw-r--r--internal/collector/script_embed.go9
2 files changed, 88 insertions, 73 deletions
diff --git a/internal/collector/collector.go b/internal/collector/collector.go
index 91f3d7b..0107d61 100644
--- a/internal/collector/collector.go
+++ b/internal/collector/collector.go
@@ -20,13 +20,13 @@ type StatsStore interface {
SetNet(host, iface string, net NetLine, stamp float64)
}
-// Run starts a collector for one host: runs the embedded remote script (local or over SSH) and parses the stream into store.
-// Host may be "host" or "host:user". It runs until ctx is cancelled or the command exits.
-// The script is embedded in the binary; no external script file is required.
+// Run starts a collector for one host: runs the embedded remote script (local or over SSH)
+// and parses the output stream into store. Host may be "host" or "host:user".
+// It runs until ctx is cancelled or the command exits.
func Run(ctx context.Context, host string, cfg *config.Config, store StatsStore) error {
hostKey, user := splitHostUser(host)
- // Select script: Only Linux supported for local monitoring
+ // Select script: only Linux is supported for local monitoring.
scriptBytes := LinuxScript
if isLocal(hostKey) {
scriptBytes = getLocalScript()
@@ -35,45 +35,67 @@ func Run(ctx context.Context, host string, cfg *config.Config, store StatsStore)
}
}
- script := bytes.NewReader(scriptBytes)
- var scanner *bufio.Scanner
+ var (
+ scanner *bufio.Scanner
+ cmd *exec.Cmd
+ err error
+ )
if isLocal(hostKey) {
- cmd := exec.CommandContext(ctx, "bash", "-s")
- cmd.Stdin = script
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return fmt.Errorf("%s: %w", hostKey, err)
- }
- if err := cmd.Start(); err != nil {
- return fmt.Errorf("%s: %w", hostKey, err)
- }
- defer cmd.Wait()
- scanner = bufio.NewScanner(stdout)
+ scanner, cmd, err = startLocalScanner(ctx, scriptBytes)
} else {
- args := []string{"-o", "StrictHostKeyChecking=no"}
- if cfg.SSHOpts != "" {
- args = append(args, strings.Fields(cfg.SSHOpts)...)
- }
- if user != "" {
- args = append(args, "-l", user)
- }
- args = append(args, hostKey, "bash -s")
- cmd := exec.CommandContext(ctx, "ssh", args...)
- cmd.Stdin = script
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return fmt.Errorf("%s: %w", hostKey, err)
- }
- if err := cmd.Start(); err != nil {
- return fmt.Errorf("%s: %w", hostKey, err)
- }
- defer cmd.Wait()
- scanner = bufio.NewScanner(stdout)
+ scanner, cmd, err = startRemoteScanner(ctx, hostKey, user, scriptBytes, cfg)
+ }
+ if err != nil {
+ return fmt.Errorf("%s: %w", hostKey, err)
}
+ defer cmd.Wait()
+
+ return parseCollectorStream(ctx, scanner, hostKey, store)
+}
+// startLocalScanner spawns "bash -s" with scriptBytes on stdin and returns a scanner
+// over its stdout along with the Cmd for deferred Wait.
+func startLocalScanner(ctx context.Context, scriptBytes []byte) (*bufio.Scanner, *exec.Cmd, error) {
+ cmd := exec.CommandContext(ctx, "bash", "-s")
+ cmd.Stdin = bytes.NewReader(scriptBytes)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, nil, err
+ }
+ if err := cmd.Start(); err != nil {
+ return nil, nil, err
+ }
+ return bufio.NewScanner(stdout), cmd, nil
+}
+
+// startRemoteScanner spawns "ssh host bash -s" with scriptBytes on stdin and returns
+// a scanner over its stdout along with the Cmd for deferred Wait.
+func startRemoteScanner(ctx context.Context, hostKey, user string, scriptBytes []byte, cfg *config.Config) (*bufio.Scanner, *exec.Cmd, error) {
+ args := []string{"-o", "StrictHostKeyChecking=no"}
+ if cfg.SSHOpts != "" {
+ args = append(args, strings.Fields(cfg.SSHOpts)...)
+ }
+ if user != "" {
+ args = append(args, "-l", user)
+ }
+ args = append(args, hostKey, "bash -s")
+ cmd := exec.CommandContext(ctx, "ssh", args...)
+ cmd.Stdin = bytes.NewReader(scriptBytes)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, nil, err
+ }
+ if err := cmd.Start(); err != nil {
+ return nil, nil, err
+ }
+ return bufio.NewScanner(stdout), cmd, nil
+}
+
+// parseCollectorStream reads lines from scanner and dispatches parsed stats to store.
+// Always collects all CPU lines (cpu, cpu0, cpu1, ...) so display can toggle per-core
+// view with key 1 without a reconnect.
+func parseCollectorStream(ctx context.Context, scanner *bufio.Scanner, hostKey string, store StatsStore) error {
mode := ""
- // Always collect all CPU lines (cpu, cpu0, cpu1, ...) so display can toggle per-core view with key 1
- cpustring := "cpu"
for scanner.Scan() {
select {
case <-ctx.Done():
@@ -85,38 +107,40 @@ func Run(ctx context.Context, host string, cfg *config.Config, store StatsStore)
mode = line
continue
}
- switch mode {
- case ModeLoadAvg:
- l := ParseLoadAvg(line)
- store.SetLoadAvg(hostKey, l.Load1, l.Load5, l.Load15)
- case ModeMemStats:
- if mem, ok := ParseMemLine(line); ok {
- store.SetMem(hostKey, mem.Key, mem.Value)
- }
- case ModeNetStats:
- if idx := strings.Index(line, ":"); idx >= 0 {
- iface := strings.TrimSpace(line[:idx])
- rest := line[idx+1:]
- net, err := ParseNetLine(iface + ":" + rest)
- if err != nil {
- continue
- }
+ dispatchCollectorLine(mode, line, hostKey, store)
+ }
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("%s: read: %w", hostKey, err)
+ }
+ return nil
+}
+
+// dispatchCollectorLine routes one parsed line to the appropriate store setter
+// based on the current protocol mode marker.
+func dispatchCollectorLine(mode, line, hostKey string, store StatsStore) {
+ switch mode {
+ case ModeLoadAvg:
+ l := ParseLoadAvg(line)
+ store.SetLoadAvg(hostKey, l.Load1, l.Load5, l.Load15)
+ case ModeMemStats:
+ if mem, ok := ParseMemLine(line); ok {
+ store.SetMem(hostKey, mem.Key, mem.Value)
+ }
+ case ModeNetStats:
+ if idx := strings.Index(line, ":"); idx >= 0 {
+ iface := strings.TrimSpace(line[:idx])
+ net, err := ParseNetLine(iface + ":" + line[idx+1:])
+ if err == nil {
store.SetNet(hostKey, net.Iface, net, float64(time.Now().UnixNano())/1e9)
}
- case ModeCPUStats:
- if strings.HasPrefix(line, cpustring) {
- cu, err := ParseCPULine(line)
- if err != nil {
- continue
- }
+ }
+ case ModeCPUStats:
+ if strings.HasPrefix(line, "cpu") {
+ if cu, err := ParseCPULine(line); err == nil {
store.SetCPU(hostKey, cu.Name, cu)
}
}
}
- if err := scanner.Err(); err != nil {
- return fmt.Errorf("%s: read: %w", hostKey, err)
- }
- return nil
}
// splitHostUser splits "host:user" into (host, user). If no colon, returns (host, "").
diff --git a/internal/collector/script_embed.go b/internal/collector/script_embed.go
deleted file mode 100644
index 168ca37..0000000
--- a/internal/collector/script_embed.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package collector
-
-import _ "embed"
-
-// RemoteScript is the loadbars-remote.sh script embedded for local and SSH execution.
-// Path is relative to this file's directory (internal/collector).
-//
-//go:embed scriptdata/loadbars-remote.sh
-var RemoteScript []byte