mirror of
https://github.com/MobAI-App/simslim.git
synced 2026-09-28 17:12:59 +02:00
A top(1) for the simulator fleet: every booted simulator with its OS, slim state, process count, CPU, RAM, and disk, refreshing live. Enter a row (or pass a UDID) to drill into that simulator's per-daemon footprint. Columns sort via n/o/s/p/c/m/d with a version-aware OS comparator; the name column flexes to the terminal width. --json and piped output emit one-shot snapshots, keeping the JSON contract for the GUI. Library side stays dependency-free: MeasureProcesses keeps per-process footprint+cpu from the shared ps/top snapshot, and FleetSnapshot composes booted devices, slim status (fanned out per device), and MeasureMany. The TUI (bubbletea + lipgloss, CLI-only deps) anchors the cursor by UDID across live re-sorts, keeps the last snapshot on transient refresh errors, and guards disk sweeps against overlap. Also adds `simslim list --booted` to filter the listing to booted simulators.
80 lines
1.7 KiB
Go
80 lines
1.7 KiB
Go
package simslim
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
// FleetSnapshot gathers the live resource view of every booted simulator: its
|
|
// slim status and memory come from one shared process snapshot; disk usage is
|
|
// optional because it shells out to `du` per device and is comparatively slow.
|
|
func FleetSnapshot(ctx context.Context, withDisk bool) (TopOutput, error) {
|
|
devices, err := ListDevices(ctx)
|
|
if err != nil {
|
|
return TopOutput{}, err
|
|
}
|
|
|
|
var booted []Device
|
|
for _, d := range devices {
|
|
if d.State == "Booted" {
|
|
booted = append(booted, d)
|
|
}
|
|
}
|
|
|
|
udids := make([]string, len(booted))
|
|
for i, d := range booted {
|
|
udids[i] = d.UDID
|
|
}
|
|
memory, memErr := MeasureMany(ctx, udids)
|
|
|
|
sims := make([]TopSim, len(booted))
|
|
var wg sync.WaitGroup
|
|
for i, d := range booted {
|
|
wg.Add(1)
|
|
go func(i int, d Device) {
|
|
defer wg.Done()
|
|
sim := TopSim{Device: d}
|
|
if st, _, err := ReadStatusForDevice(ctx, d); err == nil {
|
|
disabled := st.ManagedDisabled
|
|
sim.ManagedDisabled = &disabled
|
|
sim.ManagedTotal = st.ManagedTotal
|
|
} else {
|
|
sim.StatusError = err.Error()
|
|
}
|
|
if withDisk {
|
|
if du, err := DeviceDiskUsage(ctx, d.UDID); err == nil {
|
|
bytes := du.Bytes
|
|
sim.DiskBytes = &bytes
|
|
}
|
|
}
|
|
sims[i] = sim
|
|
}(i, d)
|
|
}
|
|
wg.Wait()
|
|
|
|
var out TopOutput
|
|
for _, sim := range sims {
|
|
if m, ok := memory[sim.UDID]; ok {
|
|
measurement := m
|
|
sim.Memory = &measurement
|
|
out.TotalBytes += m.Bytes
|
|
} else if e, ok := memErr[sim.UDID]; ok {
|
|
sim.MemoryError = e
|
|
}
|
|
out.Sims = append(out.Sims, sim)
|
|
}
|
|
|
|
sort.Slice(out.Sims, func(i, j int) bool {
|
|
return topBytes(out.Sims[i]) > topBytes(out.Sims[j])
|
|
})
|
|
return out, nil
|
|
}
|
|
|
|
func topBytes(s TopSim) int64 {
|
|
if s.Memory == nil {
|
|
return -1
|
|
}
|
|
return s.Memory.Bytes
|
|
}
|