perf: Optimize gt status from 13s to <1s (gt-zdtpw)

Key optimizations:
- Use --no-daemon for bd commands to avoid daemon IPC overhead
- Pre-fetch all agent beads in single query (ListAgentBeads)
- Pre-fetch all hook beads in single query (ShowMultiple)
- Pre-fetch all tmux sessions for O(1) lookup
- Parallel rig processing with goroutines
- Add --fast flag to skip mail lookups

The main bottleneck was bd daemon communication timing out on
stale daemon processes, causing 5+ second delays per rig.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/max
2025-12-31 12:14:09 -08:00
committed by Steve Yegge
parent 6d1d1469b2
commit 22557e6917
2 changed files with 301 additions and 181 deletions

View File

@@ -218,7 +218,10 @@ func New(workDir string) *Beads {
// run executes a bd command and returns stdout. // run executes a bd command and returns stdout.
func (b *Beads) run(args ...string) ([]byte, error) { func (b *Beads) run(args ...string) ([]byte, error) {
cmd := exec.Command("bd", args...) // Use --no-daemon for faster read operations (avoids daemon IPC overhead)
// The daemon is primarily useful for write coalescing, not reads
fullArgs := append([]string{"--no-daemon"}, args...)
cmd := exec.Command("bd", fullArgs...)
cmd.Dir = b.workDir cmd.Dir = b.workDir
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
@@ -389,6 +392,55 @@ func (b *Beads) Show(id string) (*Issue, error) {
return issues[0], nil return issues[0], nil
} }
// ShowMultiple fetches multiple issues by ID in a single bd call.
// Returns a map of ID to Issue. Missing IDs are not included in the map.
func (b *Beads) ShowMultiple(ids []string) (map[string]*Issue, error) {
if len(ids) == 0 {
return make(map[string]*Issue), nil
}
// bd show supports multiple IDs
args := append([]string{"show", "--json"}, ids...)
out, err := b.run(args...)
if err != nil {
// If bd fails, return empty map (some IDs might not exist)
return make(map[string]*Issue), nil
}
var issues []*Issue
if err := json.Unmarshal(out, &issues); err != nil {
return nil, fmt.Errorf("parsing bd show output: %w", err)
}
result := make(map[string]*Issue, len(issues))
for _, issue := range issues {
result[issue.ID] = issue
}
return result, nil
}
// ListAgentBeads returns all agent beads in a single query.
// Returns a map of agent bead ID to Issue.
func (b *Beads) ListAgentBeads() (map[string]*Issue, error) {
out, err := b.run("list", "--type=agent", "--json")
if err != nil {
return nil, err
}
var issues []*Issue
if err := json.Unmarshal(out, &issues); err != nil {
return nil, fmt.Errorf("parsing bd list output: %w", err)
}
result := make(map[string]*Issue, len(issues))
for _, issue := range issues {
result[issue.ID] = issue
}
return result, nil
}
// Blocked returns issues that are blocked by dependencies. // Blocked returns issues that are blocked by dependencies.
func (b *Beads) Blocked() ([]*Issue, error) { func (b *Beads) Blocked() ([]*Issue, error) {
out, err := b.run("blocked", "--json") out, err := b.run("blocked", "--json")

View File

@@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads" "github.com/steveyegge/gastown/internal/beads"
@@ -21,6 +22,7 @@ import (
) )
var statusJSON bool var statusJSON bool
var statusFast bool
var statusCmd = &cobra.Command{ var statusCmd = &cobra.Command{
Use: "status", Use: "status",
@@ -29,12 +31,15 @@ var statusCmd = &cobra.Command{
Short: "Show overall town status", Short: "Show overall town status",
Long: `Display the current status of the Gas Town workspace. Long: `Display the current status of the Gas Town workspace.
Shows town name, registered rigs, active polecats, and witness status.`, Shows town name, registered rigs, active polecats, and witness status.
Use --fast to skip mail lookups for faster execution.`,
RunE: runStatus, RunE: runStatus,
} }
func init() { func init() {
statusCmd.Flags().BoolVar(&statusJSON, "json", false, "Output as JSON") statusCmd.Flags().BoolVar(&statusJSON, "json", false, "Output as JSON")
statusCmd.Flags().BoolVar(&statusFast, "fast", false, "Skip mail lookups for faster execution")
rootCmd.AddCommand(statusCmd) rootCmd.AddCommand(statusCmd)
} }
@@ -144,6 +149,14 @@ func runStatus(cmd *cobra.Command, args []string) error {
// Create tmux instance for runtime checks // Create tmux instance for runtime checks
t := tmux.NewTmux() t := tmux.NewTmux()
// Pre-fetch all tmux sessions for O(1) lookup
allSessions := make(map[string]bool)
if sessions, err := t.ListSessions(); err == nil {
for _, s := range sessions {
allSessions[s] = true
}
}
// Discover rigs // Discover rigs
rigs, err := mgr.DiscoverRigs() rigs, err := mgr.DiscoverRigs()
if err != nil { if err != nil {
@@ -154,6 +167,25 @@ func runStatus(cmd *cobra.Command, args []string) error {
gastownBeadsPath := filepath.Join(townRoot, "gastown", "mayor", "rig") gastownBeadsPath := filepath.Join(townRoot, "gastown", "mayor", "rig")
agentBeads := beads.New(gastownBeadsPath) agentBeads := beads.New(gastownBeadsPath)
// Pre-fetch all agent beads in a single query for performance
allAgentBeads, _ := agentBeads.ListAgentBeads()
if allAgentBeads == nil {
allAgentBeads = make(map[string]*beads.Issue)
}
// Pre-fetch all hook beads (referenced in agent beads) in a single query
var allHookIDs []string
for _, issue := range allAgentBeads {
fields := beads.ParseAgentFields(issue.Description)
if fields != nil && fields.HookBead != "" {
allHookIDs = append(allHookIDs, fields.HookBead)
}
}
allHookBeads, _ := agentBeads.ShowMultiple(allHookIDs)
if allHookBeads == nil {
allHookBeads = make(map[string]*beads.Issue)
}
// Create mail router for inbox lookups // Create mail router for inbox lookups
mailRouter := mail.NewRouter(townRoot) mailRouter := mail.NewRouter(townRoot)
@@ -173,57 +205,79 @@ func runStatus(cmd *cobra.Command, args []string) error {
} }
} }
// Build status // Build status - parallel fetch global agents and rigs
status := TownStatus{ status := TownStatus{
Name: townConfig.Name, Name: townConfig.Name,
Location: townRoot, Location: townRoot,
Overseer: overseerInfo, Overseer: overseerInfo,
Agents: discoverGlobalAgents(t, agentBeads, mailRouter), Rigs: make([]RigStatus, len(rigs)),
Rigs: make([]RigStatus, 0, len(rigs)),
} }
for _, r := range rigs { var wg sync.WaitGroup
rs := RigStatus{
Name: r.Name,
Polecats: r.Polecats,
PolecatCount: len(r.Polecats),
HasWitness: r.HasWitness,
HasRefinery: r.HasRefinery,
}
// Count crew workers // Fetch global agents in parallel with rig discovery
crewGit := git.NewGit(r.Path) wg.Add(1)
crewMgr := crew.NewManager(r, crewGit) go func() {
if workers, err := crewMgr.List(); err == nil { defer wg.Done()
for _, w := range workers { status.Agents = discoverGlobalAgents(allSessions, allAgentBeads, allHookBeads, mailRouter, statusFast)
rs.Crews = append(rs.Crews, w.Name) }()
// Process all rigs in parallel
rigActiveHooks := make([]int, len(rigs)) // Track hooks per rig for thread safety
for i, r := range rigs {
wg.Add(1)
go func(idx int, r *rig.Rig) {
defer wg.Done()
rs := RigStatus{
Name: r.Name,
Polecats: r.Polecats,
PolecatCount: len(r.Polecats),
HasWitness: r.HasWitness,
HasRefinery: r.HasRefinery,
} }
rs.CrewCount = len(workers)
}
// Discover hooks for all agents in this rig // Count crew workers
rs.Hooks = discoverRigHooks(r, rs.Crews) crewGit := git.NewGit(r.Path)
for _, hook := range rs.Hooks { crewMgr := crew.NewManager(r, crewGit)
if hook.HasWork { if workers, err := crewMgr.List(); err == nil {
status.Summary.ActiveHooks++ for _, w := range workers {
rs.Crews = append(rs.Crews, w.Name)
}
rs.CrewCount = len(workers)
} }
}
// Discover runtime state for all agents in this rig // Discover hooks for all agents in this rig
rs.Agents = discoverRigAgents(t, r, rs.Crews, agentBeads, mailRouter) rs.Hooks = discoverRigHooks(r, rs.Crews)
activeHooks := 0
for _, hook := range rs.Hooks {
if hook.HasWork {
activeHooks++
}
}
rigActiveHooks[idx] = activeHooks
// Get MQ summary if rig has a refinery // Discover runtime state for all agents in this rig
rs.MQ = getMQSummary(r) rs.Agents = discoverRigAgents(allSessions, r, rs.Crews, allAgentBeads, allHookBeads, mailRouter, statusFast)
status.Rigs = append(status.Rigs, rs) // Get MQ summary if rig has a refinery
rs.MQ = getMQSummary(r)
// Update summary status.Rigs[idx] = rs
status.Summary.PolecatCount += len(r.Polecats) }(i, r)
}
wg.Wait()
// Aggregate summary (after parallel work completes)
for i, rs := range status.Rigs {
status.Summary.PolecatCount += rs.PolecatCount
status.Summary.CrewCount += rs.CrewCount status.Summary.CrewCount += rs.CrewCount
if r.HasWitness { status.Summary.ActiveHooks += rigActiveHooks[i]
if rs.HasWitness {
status.Summary.WitnessCount++ status.Summary.WitnessCount++
} }
if r.HasRefinery { if rs.HasRefinery {
status.Summary.RefineryCount++ status.Summary.RefineryCount++
} }
} }
@@ -529,58 +583,73 @@ func discoverRigHooks(r *rig.Rig, crews []string) []AgentHookInfo {
} }
// discoverGlobalAgents checks runtime state for town-level agents (Mayor, Deacon). // discoverGlobalAgents checks runtime state for town-level agents (Mayor, Deacon).
func discoverGlobalAgents(t *tmux.Tmux, agentBeads *beads.Beads, mailRouter *mail.Router) []AgentRuntime { // Uses parallel fetching for performance. If skipMail is true, mail lookups are skipped.
var agents []AgentRuntime // allSessions is a preloaded map of tmux sessions for O(1) lookup.
// allAgentBeads is a preloaded map of agent beads for O(1) lookup.
// Check Mayor // allHookBeads is a preloaded map of hook beads for O(1) lookup.
mayorRunning, _ := t.HasSession(MayorSessionName) func discoverGlobalAgents(allSessions map[string]bool, allAgentBeads map[string]*beads.Issue, allHookBeads map[string]*beads.Issue, mailRouter *mail.Router, skipMail bool) []AgentRuntime {
mayor := AgentRuntime{ // Define agents to discover
Name: "mayor", agentDefs := []struct {
Address: "mayor/", name string
Session: MayorSessionName, address string
Role: "coordinator", session string
Running: mayorRunning, role string
beadID string
}{
{"mayor", "mayor/", MayorSessionName, "coordinator", "gt-mayor"},
{"deacon", "deacon/", DeaconSessionName, "health-check", "gt-deacon"},
} }
// Look up agent bead for hook/state
if issue, fields, err := agentBeads.GetAgentBead("gt-mayor"); err == nil && issue != nil { agents := make([]AgentRuntime, len(agentDefs))
mayor.HookBead = fields.HookBead var wg sync.WaitGroup
mayor.State = fields.AgentState
if fields.HookBead != "" { for i, def := range agentDefs {
mayor.HasWork = true wg.Add(1)
// Try to get the title of the pinned bead go func(idx int, d struct {
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil { name string
mayor.WorkTitle = pinnedIssue.Title address string
session string
role string
beadID string
}) {
defer wg.Done()
agent := AgentRuntime{
Name: d.name,
Address: d.address,
Session: d.session,
Role: d.role,
} }
}
}
// Get mail info
populateMailInfo(&mayor, mailRouter)
agents = append(agents, mayor)
// Check Deacon // Check tmux session from preloaded map (O(1))
deaconRunning, _ := t.HasSession(DeaconSessionName) agent.Running = allSessions[d.session]
deacon := AgentRuntime{
Name: "deacon", // Look up agent bead from preloaded map (O(1))
Address: "deacon/", if issue, ok := allAgentBeads[d.beadID]; ok {
Session: DeaconSessionName, fields := beads.ParseAgentFields(issue.Description)
Role: "health-check", if fields != nil {
Running: deaconRunning, agent.HookBead = fields.HookBead
} agent.State = fields.AgentState
// Look up agent bead for hook/state if fields.HookBead != "" {
if issue, fields, err := agentBeads.GetAgentBead("gt-deacon"); err == nil && issue != nil { agent.HasWork = true
deacon.HookBead = fields.HookBead // Get hook title from preloaded map
deacon.State = fields.AgentState if pinnedIssue, ok := allHookBeads[fields.HookBead]; ok {
if fields.HookBead != "" { agent.WorkTitle = pinnedIssue.Title
deacon.HasWork = true }
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil { }
deacon.WorkTitle = pinnedIssue.Title }
} }
}
}
// Get mail info
populateMailInfo(&deacon, mailRouter)
agents = append(agents, deacon)
// Get mail info (skip if --fast)
if !skipMail {
populateMailInfo(&agent, mailRouter)
}
agents[idx] = agent
}(i, def)
}
wg.Wait()
return agents return agents
} }
@@ -602,118 +671,117 @@ func populateMailInfo(agent *AgentRuntime, router *mail.Router) {
} }
} }
// agentDef defines an agent to discover
type agentDef struct {
name string
address string
session string
role string
beadID string
}
// discoverRigAgents checks runtime state for all agents in a rig. // discoverRigAgents checks runtime state for all agents in a rig.
func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *beads.Beads, mailRouter *mail.Router) []AgentRuntime { // Uses parallel fetching for performance. If skipMail is true, mail lookups are skipped.
var agents []AgentRuntime // allSessions is a preloaded map of tmux sessions for O(1) lookup.
// allAgentBeads is a preloaded map of agent beads for O(1) lookup.
// allHookBeads is a preloaded map of hook beads for O(1) lookup.
func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string, allAgentBeads map[string]*beads.Issue, allHookBeads map[string]*beads.Issue, mailRouter *mail.Router, skipMail bool) []AgentRuntime {
// Build list of all agents to discover
var defs []agentDef
// Check Witness // Witness
if r.HasWitness { if r.HasWitness {
sessionName := witnessSessionName(r.Name) defs = append(defs, agentDef{
running, _ := t.HasSession(sessionName) name: "witness",
witness := AgentRuntime{ address: r.Name + "/witness",
Name: "witness", session: witnessSessionName(r.Name),
Address: r.Name + "/witness", role: "witness",
Session: sessionName, beadID: beads.WitnessBeadID(r.Name),
Role: "witness", })
Running: running,
}
// Look up agent bead
agentID := beads.WitnessBeadID(r.Name)
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
witness.HookBead = fields.HookBead
witness.State = fields.AgentState
if fields.HookBead != "" {
witness.HasWork = true
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil {
witness.WorkTitle = pinnedIssue.Title
}
}
}
populateMailInfo(&witness, mailRouter)
agents = append(agents, witness)
} }
// Check Refinery // Refinery
if r.HasRefinery { if r.HasRefinery {
sessionName := fmt.Sprintf("gt-%s-refinery", r.Name) defs = append(defs, agentDef{
running, _ := t.HasSession(sessionName) name: "refinery",
refinery := AgentRuntime{ address: r.Name + "/refinery",
Name: "refinery", session: fmt.Sprintf("gt-%s-refinery", r.Name),
Address: r.Name + "/refinery", role: "refinery",
Session: sessionName, beadID: beads.RefineryBeadID(r.Name),
Role: "refinery", })
Running: running,
}
// Look up agent bead
agentID := beads.RefineryBeadID(r.Name)
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
refinery.HookBead = fields.HookBead
refinery.State = fields.AgentState
if fields.HookBead != "" {
refinery.HasWork = true
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil {
refinery.WorkTitle = pinnedIssue.Title
}
}
}
populateMailInfo(&refinery, mailRouter)
agents = append(agents, refinery)
} }
// Check Polecats // Polecats
for _, name := range r.Polecats { for _, name := range r.Polecats {
sessionName := fmt.Sprintf("gt-%s-%s", r.Name, name) defs = append(defs, agentDef{
running, _ := t.HasSession(sessionName) name: name,
polecat := AgentRuntime{ address: r.Name + "/" + name,
Name: name, session: fmt.Sprintf("gt-%s-%s", r.Name, name),
Address: r.Name + "/" + name, role: "polecat",
Session: sessionName, beadID: beads.PolecatBeadID(r.Name, name),
Role: "polecat", })
Running: running,
}
// Look up agent bead
agentID := beads.PolecatBeadID(r.Name, name)
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
polecat.HookBead = fields.HookBead
polecat.State = fields.AgentState
if fields.HookBead != "" {
polecat.HasWork = true
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil {
polecat.WorkTitle = pinnedIssue.Title
}
}
}
populateMailInfo(&polecat, mailRouter)
agents = append(agents, polecat)
} }
// Check Crew // Crew
for _, name := range crews { for _, name := range crews {
sessionName := crewSessionName(r.Name, name) defs = append(defs, agentDef{
running, _ := t.HasSession(sessionName) name: name,
crewAgent := AgentRuntime{ address: r.Name + "/crew/" + name,
Name: name, session: crewSessionName(r.Name, name),
Address: r.Name + "/crew/" + name, role: "crew",
Session: sessionName, beadID: beads.CrewBeadID(r.Name, name),
Role: "crew", })
Running: running,
}
// Look up agent bead
agentID := beads.CrewBeadID(r.Name, name)
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
crewAgent.HookBead = fields.HookBead
crewAgent.State = fields.AgentState
if fields.HookBead != "" {
crewAgent.HasWork = true
if pinnedIssue, err := agentBeads.Show(fields.HookBead); err == nil {
crewAgent.WorkTitle = pinnedIssue.Title
}
}
}
populateMailInfo(&crewAgent, mailRouter)
agents = append(agents, crewAgent)
} }
if len(defs) == 0 {
return nil
}
// Fetch all agents in parallel
agents := make([]AgentRuntime, len(defs))
var wg sync.WaitGroup
for i, def := range defs {
wg.Add(1)
go func(idx int, d agentDef) {
defer wg.Done()
agent := AgentRuntime{
Name: d.name,
Address: d.address,
Session: d.session,
Role: d.role,
}
// Check tmux session from preloaded map (O(1))
agent.Running = allSessions[d.session]
// Look up agent bead from preloaded map (O(1))
if issue, ok := allAgentBeads[d.beadID]; ok {
fields := beads.ParseAgentFields(issue.Description)
if fields != nil {
agent.HookBead = fields.HookBead
agent.State = fields.AgentState
if fields.HookBead != "" {
agent.HasWork = true
// Get hook title from preloaded map
if pinnedIssue, ok := allHookBeads[fields.HookBead]; ok {
agent.WorkTitle = pinnedIssue.Title
}
}
}
}
// Get mail info (skip if --fast)
if !skipMail {
populateMailInfo(&agent, mailRouter)
}
agents[idx] = agent
}(i, def)
}
wg.Wait()
return agents return agents
} }