Documentation
¶
Overview ¶
Package controltower renders the Control Tower snapshot: a safe, redacted view of the server runtime, the runner fleet, and per-pool busy/idle counts.
The snapshot is read-only and project-scoped in Phase R1. The contract is the JSON shape documented in .ai/plans/system-stats-page.md (Phase R1): the frontend mirrors every json tag 1:1, so every struct field carries an explicit snake_case tag. OperatorDiagnostics is NEVER projected into a response (model.go:188 on AutomationRun); only SafeSummary is.
Index ¶
Constants ¶
const ( ScopeProject = "project" ScopeGlobal = "global" ScopeAll = "all" )
Scope labels which runner population the snapshot covers. Phase R1 only implements ScopeProject; global/all arrive in R1b.
const ( PoolIDGeneric = "mivia-automation-runner" PoolIDImplReviewer = "mivia-automation-runner-impl-reviewer" PoolIDValReviewer = "mivia-automation-runner-val-reviewer" PoolIDPlanReviewer = "mivia-automation-runner-plan-reviewer" PoolIDOrchestrator = "mivia-automation-runner-orchestrator" PoolIDCrush = "mivia-automation-runner-crush" )
Pool IDs mirror the six services in docker-compose.yml:106-442 (the "Runner fleet topology" table in the plan is the source of truth). These are stable identifiers the frontend and scaler both key off.
const ( RunnerStatusBusy = "busy" RunnerStatusStale = "stale" RunnerStatusOther = "other" )
Runner row statuses. The runner is "busy" when it has at least one executing-status run; "stale" when its lease expired or heartbeat went quiet; otherwise its status reflects what it is doing. Idle runners are not enumerable until the heartbeat registry lands (Phase R2).
const ( WarningNoProjectsRegistered = "no_projects_registered" )
Warnings are stable, machine-readable strings; the frontend renders a localized message for each. They never carry raw error text.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ControlTowerSnapshot ¶
type ControlTowerSnapshot struct {
Scope string `json:"scope"`
ProjectID string `json:"project_id,omitempty"`
Server ServerStats `json:"server"`
Pools []PoolStats `json:"pools"`
Runners RunnerStats `json:"runners"`
GPU GPUStats `json:"gpu"`
Warnings []string `json:"warnings,omitempty"`
CheckedAt time.Time `json:"checked_at"`
}
ControlTowerSnapshot is the top-level response for GET /api/v1/projects/{id}/control-tower. The shape is the Phase R1 contract from the plan: server runtime, the six pools, runner aggregates, and GPU (present:false until R3).
type GPUDevice ¶
type GPUDevice struct {
Index int `json:"index"`
Name string `json:"name,omitempty"`
MemoryTotal uint64 `json:"memory_total_bytes"`
MemoryUsed uint64 `json:"memory_used_bytes"`
Utilization float64 `json:"utilization_percent"`
}
GPUDevice is one GPU. Empty until Phase R3.
type GPUStats ¶
GPUStats reports GPU presence and devices. Always {present:false, devices:[]} until Phase R3 wires the lazy NVML dlopen.
type PoolSpec ¶
PoolSpec is the identity of one runner pool. The six pools are fixed in docker-compose.yml:106-442; the plan's "Runner fleet topology" table is the source of truth for id/kind/agent_id/max_concurrent. Label is the human-readable name shown in the UI.
type PoolStats ¶
type PoolStats struct {
ID string `json:"id"`
Kind string `json:"kind"`
AgentID string `json:"agent_id,omitempty"`
Label string `json:"label"`
Busy int `json:"busy"`
Idle int `json:"idle"`
Stale int `json:"stale"`
DesiredScale *int `json:"desired_scale"`
ActualScale *int `json:"actual_scale"`
MaxConcurrent int `json:"max_concurrent"`
}
PoolStats is one row per runner pool. desired_scale/actual_scale are nil until Phase S wires the scaler; max_concurrent mirrors docker-compose.yml.
type ProjectLister ¶
type ProjectLister interface {
List() []projectregistry.Project
}
ProjectLister is the registry seam. projectregistry.Registry satisfies this structurally (Registry.List at projectregistry/model.go:86). Project scope never enumerates the registry in R1, but the Service holds one for the global/all scope arriving in R1b.
type RunListerForStats ¶
type RunListerForStats interface {
ListRunsForStats(ctx context.Context, filter projectautomation.RunFilter) ([]projectautomation.AutomationRun, error)
}
RunListerForStats is the read seam the snapshot uses to list automation runs WITHOUT the reconcile side effects that Service.ListRuns triggers (reconcileRunningRuns + reconcileVerifyingRuns). projectautomation.Service satisfies this structurally via its ListRunsForStats method (added in this phase); tests pass a counting fake to assert cardinality (A13).
type RunnerCurrentRun ¶
type RunnerCurrentRun struct {
RunID string `json:"run_id"`
ProjectID string `json:"project_id"`
TaskID string `json:"task_id,omitempty"`
AgentID string `json:"agent_id,omitempty"`
Status string `json:"status"`
ChainRunID string `json:"chain_run_id,omitempty"`
StartedAt *time.Time `json:"started_at,omitempty"`
AttemptCount int `json:"attempt_count"`
SafeSummary string `json:"safe_summary,omitempty"`
}
RunnerCurrentRun is the safe projection of an AutomationRun driving a busy runner. It deliberately omits OperatorDiagnostics (model.go:188) — only SafeSummary is exposed.
type RunnerResources ¶
type RunnerResources struct {
CPUPercent float64 `json:"cpu_percent"`
MemBytes uint64 `json:"mem_bytes"`
}
RunnerResources is the per-runner CPU/mem snapshot (Phase R2). Nil in R1.
type RunnerStats ¶
type RunnerStats struct {
Total int `json:"total"`
Busy int `json:"busy"`
Idle int `json:"idle"`
Stale int `json:"stale"`
ByKind map[string]int `json:"by_kind"`
Items []RunnerSummary `json:"items"`
}
RunnerStats is the aggregate runner view plus the per-runner rows.
type RunnerSummary ¶
type RunnerSummary struct {
RunnerID string `json:"runner_id"`
Kind string `json:"kind"`
AgentID string `json:"agent_id,omitempty"`
PoolID string `json:"pool_id"`
ProjectIDs []string `json:"project_ids,omitempty"`
Status string `json:"status"`
LastHeartbeatAt *time.Time `json:"last_heartbeat_at,omitempty"`
LeaseExpiresAt *time.Time `json:"lease_expires_at,omitempty"`
CurrentRuns []RunnerCurrentRun `json:"current_runs,omitempty"`
Resources *RunnerResources `json:"resources"`
}
RunnerSummary is one runner's view. Multiple concurrent runs on the same runner collapse into one row with multiple current_runs (grouped by full RunnerID, no -wN suffix collapsing — main.go:1602-1642). Resources is nil until Phase R2.
ProjectIDs carries the distinct set of projects this runner's current_runs belong to. It is populated only in global/all scope (where a single row can span multiple projects — C12) and omitted in project scope (where the top-level snapshot.project_id already carries it). The FE renders this as a per-row project badge (C13). The per-run RunnerCurrentRun.ProjectID remains the authoritative single-run attribution.
type ServerStats ¶
type ServerStats struct {
Goroutines int `json:"goroutines"`
HeapAllocBytes uint64 `json:"heap_alloc_bytes"`
HeapInuseBytes uint64 `json:"heap_inuse_bytes"`
StackInuseBytes uint64 `json:"stack_inuse_bytes"`
CgroupMemBytes uint64 `json:"cgroup_mem_bytes"`
HostMemTotalBytes uint64 `json:"host_mem_total_bytes"`
}
ServerStats reports the mivia-server process runtime. Cgroup/host fields are zero until Phase R3 wires the cgroup/meminfo readers.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service renders the Control Tower snapshot. It owns no mutable runner state in R1 (the idle registry arrives in R2); every Snapshot call is stateless aside from the runtime.MemStats read.
func NewService ¶
func NewService(runLister RunListerForStats, registry ProjectLister) *Service
NewService wires the run lister and registry. now is injected (default time.Now) so tests can fix the timestamp for A19 (checked_at is UTC).
func (*Service) Snapshot ¶
func (svc *Service) Snapshot(ctx context.Context, scope, projectID string) ControlTowerSnapshot
Snapshot renders the Control Tower view for a scope. Phase R1 implements ScopeProject only; global/all arrive in R1b (they fan out across the registry, one ListRunsForStats per project — A14).
The method is read-only and side-effect-free: it calls the run lister once for the project scope (A13), groups in memory, and never touches OperatorDiagnostics (A15).