Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 107 additions & 17 deletions cmd/controlcenter/cc/assets/templates/crd.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
<div class="cc-stat-card">
<div class="cc-stat-label">Queue</div>
<div class="cc-stat-value font-mono">{{ .CRD.QueueDepth }}</div>
<div class="cc-stat-sub">Max: {{ .CRD.MaxQueueDepth }}</div>
<div class="cc-stat-sub">Limit: {{ .CRD.MaxQueueDepth }}</div>
</div>
<div class="cc-stat-card">
<div class="cc-stat-label">Reconciles</div>
Expand Down Expand Up @@ -165,10 +165,13 @@ <h2 class="cc-section-title" style="margin:0">Worker Pool</h2>

<div style="background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px">
<div style="font-size:12px;color:var(--text-muted);margin-bottom:8px">
{{ .CRD.WorkersProcessing }} of {{ .CRD.Workers }} workers actively processing
{{ .CRD.WorkersProcessing }} of {{ .CRD.WorkersActive }} workers actively processing
{{ if ne .CRD.WorkersActive .CRD.Workers }}<span style="font-style:italic">(scaled from {{ .CRD.Workers }})</span>{{ end }}
</div>
<div class="cc-progress-bar-wrap" style="height:8px;margin-bottom:16px">
<div class="cc-progress-bar started" style="width:{{ div (mul .CRD.WorkersProcessing 100) .CRD.Workers }}%"></div>
{{ if gt .CRD.WorkersActive 0 }}
<div class="cc-progress-bar started" style="width:{{ div (mul .CRD.WorkersProcessing 100) .CRD.WorkersActive }}%"></div>
{{ end }}
</div>

<div class="cc-worker-grid" id="workerGrid">
Expand Down Expand Up @@ -231,7 +234,7 @@ <h2 class="cc-section-title" style="margin:0">Worker Pool</h2>
</div>

<!-- Autoscaler Info — only shown when autoscale is declared -->
{{ if .CRD.AutoscalerEnabled }}
{{ if and .CRD.AutoscalerEnabled .CRD.AutoscalerWorkers }}
{{ $aw := .CRD.AutoscalerWorkers }}
<div class="cc-section">
<div class="cc-section-header">
Expand Down Expand Up @@ -275,26 +278,110 @@ <h2 class="cc-section-title" style="margin:0">
</div>

<!-- Expanded autoscale config — hidden by default -->
<div id="autoscalerDetail" class="hidden" style="margin-top:8px;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;font-size:13px">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
<div>
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);margin-bottom:8px">Capacity</div>
<div id="autoscalerDetail" class="hidden"
style="margin-top:8px;background:var(--bg-surface);border:1px solid var(--border);
border-radius:var(--radius);padding:20px;font-size:13px">

<!-- Card grid -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px">

<!-- Workers Card -->
<div style="background:var(--bg-elevated);border:1px solid var(--border);
border-radius:var(--radius);padding:16px">
<div style="display:flex;align-items:center;gap:6px;
font-size:11px;font-weight:600;text-transform:uppercase;
letter-spacing:0.05em;color:var(--text-muted);margin-bottom:8px">
👷 Workers
</div>
<table style="width:100%;border-collapse:collapse">
<tr><td style="color:var(--text-muted);padding:3px 0">Configured</td><td style="text-align:right;font-weight:500">{{ $aw.Configured }} workers</td></tr>
{{ if $aw.OverrideActive }}<tr><td style="color:var(--color-started);padding:3px 0">Override</td><td style="text-align:right;font-weight:500;color:var(--color-started)">{{ $aw.OverrideWorkers }} workers</td></tr>{{ end }}
<tr><td style="color:var(--text-muted);padding:3px 0">Max (pool)</td><td style="text-align:right;font-weight:500">{{ $aw.Max }}</td></tr>
<tr>
<td style="color:var(--text-muted);padding:3px 0">Now</td>
<td style="text-align:right;font-weight:500">
{{ $aw.OverrideWorkers }}
</td>
</tr>
<tr>
<td style="color:var(--text-muted);padding:3px 0">Baseline</td>
<td style="text-align:right;font-weight:500">
{{ $aw.Configured }}
</td>
</tr>
<tr>
<td style="color:var(--text-muted);padding:3px 0">Max</td>
<td style="text-align:right;font-weight:500">
{{ $aw.Max }}
</td>
</tr>
</table>
</div>
<div>
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);margin-bottom:8px">Queue</div>

<!-- Queue Card -->
<div style="background:var(--bg-elevated);border:1px solid var(--border);
border-radius:var(--radius);padding:16px">
<div style="display:flex;align-items:center;gap:6px;
font-size:11px;font-weight:600;text-transform:uppercase;
letter-spacing:0.05em;color:var(--text-muted);margin-bottom:8px">
📥 Queue
</div>
<table style="width:100%;border-collapse:collapse">
<tr>
<td style="color:var(--text-muted);padding:3px 0">Depth</td>
<td style="text-align:right;font-weight:500">
{{ $aw.QueueDepth }}
</td>
</tr>
<tr>
<td style="color:var(--text-muted);padding:3px 0">Limit</td>
<td style="text-align:right;font-weight:500">
{{ if gt .CRD.MaxQueueDepth 0 }}{{ .CRD.MaxQueueDepth }}{{ else }}∞{{ end }}
</td>
</tr>
{{ if $aw.OverrideActive }}
<tr>
<td style="color:var(--color-started);padding:3px 0">Override</td>
<td style="text-align:right;font-weight:500;color:var(--color-started)">
{{ $aw.QueueDepthEffective }}
</td>
</tr>
{{ end }}
</table>
</div>

<!-- Resync Card -->
<div style="background:var(--bg-elevated);border:1px solid var(--border);
border-radius:var(--radius);padding:16px">
<div style="display:flex;align-items:center;gap:6px;
font-size:11px;font-weight:600;text-transform:uppercase;
letter-spacing:0.05em;color:var(--text-muted);margin-bottom:8px">
🔁 Resync
</div>
<table style="width:100%;border-collapse:collapse">
<tr><td style="color:var(--text-muted);padding:3px 0">Depth now</td><td style="text-align:right;font-weight:500">{{ $aw.QueueDepth }}</td></tr>
<tr><td style="color:var(--text-muted);padding:3px 0">Configured limit</td><td style="text-align:right;font-weight:500">{{ if gt $aw.QueueDepthConfigured 0 }}{{ $aw.QueueDepthConfigured }}{{ else }}∞{{ end }}</td></tr>
{{ if $aw.OverrideActive }}<tr><td style="color:var(--color-started);padding:3px 0">Effective limit</td><td style="text-align:right;font-weight:500;color:var(--color-started)">{{ $aw.QueueDepthEffective }}</td></tr>{{ end }}
<tr>
<td style="color:var(--text-muted);padding:3px 0">Now</td>
<td style="text-align:right;font-weight:500">
{{ $aw.ResyncEffective }}
</td>
</tr>
<tr>
<td style="color:var(--text-muted);padding:3px 0">Baseline</td>
<td style="text-align:right;font-weight:500">
{{ $aw.ResyncConfigured }}
</td>
</tr>
{{ if $aw.OverrideActive }}
<tr>
<td style="color:var(--color-started);padding:3px 0">Override</td>
<td style="text-align:right;font-weight:500;color:var(--color-started)">
{{ $aw.ResyncEffective }}
</td>
</tr>
{{ end }}
</table>
</div>

</div>
</div>
</div>
</div>
{{ end }}

Expand Down Expand Up @@ -350,7 +437,10 @@ <h2 class="cc-section-title">Queue Pressure</h2>
<div style="background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px">
<div class="flex items-center justify-between mb-2">
<span style="font-size:12px;color:var(--text-muted)">Queue Depth</span>
<span class="font-mono" style="font-size:13px;color:var(--text-primary)">{{ .CRD.QueueDepth }} / {{ .CRD.MaxQueueDepth }}</span>
<span class="font-mono" style="font-size:13px;color:var(--text-primary)">
{{ .CRD.QueueDepth }} / {{ .CRD.MaxQueueDepth }}
{{ if gt $queuePercent 0 }}<span style="color:var(--text-muted);font-size:11px;margin-left:4px">({{ $queuePercent }}%)</span>{{ end }}
</span>
</div>
<div class="cc-progress-bar-wrap" style="height:10px">
<div class="cc-progress-bar{{ if gt $queuePercent 80 }} degraded{{ else if gt $queuePercent 50 }} pending{{ end }}"
Expand Down
1 change: 1 addition & 0 deletions cmd/controlcenter/cc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (c *Client) FetchCRDDetail(name string) (*CRDDetail, error) {
ResyncSource: info.ResyncSource,
QueueDepth: info.QueueDepth,
MaxQueueDepth: info.MaxQueueDepth,
MaxQueueDepthSource: info.MaxQueueDepthSource,
ResourceCount: info.ResourceCount,
TotalReconciles: info.TotalReconciles,
OperatorBox: info.OperatorBox,
Expand Down
3 changes: 3 additions & 0 deletions cmd/controlcenter/cc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ type AutoscalerWorkersInfo struct {
QueueDepth int64 `json:"queueDepth"`
QueueDepthConfigured int `json:"queueDepthConfigured"`
QueueDepthEffective int `json:"queueDepthEffective"`
Resync string `json:"resync"`
ResyncEffective string `json:"resyncEffective"`
ResyncConfigured string `json:"resyncConfigured"`
BusyPercent float64 `json:"busyPercent"`
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/internal/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func printBanner(kfg *orkestraKfg, konductor string) {
if crd.Workers > 0 {
fmt.Printf(" Workers: %d\n", crd.Workers)
} else {
fmt.Printf(" Workers: %d (default)\n", kfg.konfig.Cluster().DefaultWorkers)
fmt.Printf(" Workers: %d (default)\n", kfg.konfig.Katalog().DefaultWorkers)
}

if crd.Queue.MaxQueueDepth > 0 {
Expand All @@ -141,7 +141,7 @@ func printBanner(kfg *orkestraKfg, konductor string) {
fmt.Printf(" Resync: %s\n", crd.Resync.String())
} else {
fmt.Printf(" Resync: %s (default)\n",
kfg.konfig.Cluster().DefaultResync)
kfg.konfig.Katalog().DefaultResync)
}

if len(crd.DependsOn) > 0 {
Expand Down
31 changes: 16 additions & 15 deletions cmd/internal/konstructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,23 @@ func konstructOrkestra(kfg *konfig.Konfig, m *merger.Merger, ctx context.Context
// enabling cross-CRD observation with zero API server calls.
ktrlRegistry := kordinator.NewKordinatorRegistry()

// ── 4e. CRD health map ────────────────────────────────────────────────────
// One CRDHealth per CRD — shared between the DependencyKordinator
// (which updates it on each reconcile) and the HTTP health routes
// (which read it on each request). All three reference the same pointers.
crdHealthMap := make(map[string]*kordinator.CRDHealth)
for _, crd := range kat.Enabled() {
gvk := crd.GVK().String()
crdHealthMap[gvk] = kordinator.NewCRDHealth(crd.Name)
}

logger.Debug().Msg("wiring CRDs into kordinator registry...")

finalizers := kfg.Finalizers()
for _, crd := range kat.Enabled() {
crd := crd
gvk := crd.GVK().String()
crd.Workers = crd.SetWorkers(kfg.Cluster().DefaultWorkers)
crd.Workers = crd.SetWorkers(kfg.Katalog().DefaultWorkers)

object, _ := crd.GetRuntimeObjects()

Expand Down Expand Up @@ -408,6 +418,7 @@ func konstructOrkestra(kfg *konfig.Konfig, m *merger.Merger, ctx context.Context
return objCopy.DeepCopyObject().(domain.Object)
},
ktrlRegistry, // cross-CRD informer lookup via GetInformerByName
crdHealthMap, // cross-CRD health map via HealthProvider
providerRegistry, // aws:, mongodb:, etc. block dispatch
pStats, // per-CRD provider error rate tracking
)
Expand All @@ -432,17 +443,7 @@ func konstructOrkestra(kfg *konfig.Konfig, m *merger.Merger, ctx context.Context
logger.Debug().Str("gvk", gvk).Msg("CRD registered")
}

// ── 5a. CRD health map ────────────────────────────────────────────────────
// One CRDHealth per CRD — shared between the DependencyKordinator
// (which updates it on each reconcile) and the HTTP health routes
// (which read it on each request). All three reference the same pointers.
crdHealthMap := make(map[string]*kordinator.CRDHealth)
for _, crd := range kat.Enabled() {
gvk := crd.GVK().String()
crdHealthMap[gvk] = kordinator.NewCRDHealth(crd.Name)
}

// ── 5b. HTTP routes ───────────────────────────────────────────────────────
// ── 5. HTTP routes ───────────────────────────────────────────────────────
// All routes registered before hs.Start() — the mux is shared.
//
// Per-CRD routes:
Expand Down Expand Up @@ -549,9 +550,9 @@ func konstructOrkestra(kfg *konfig.Konfig, m *merger.Merger, ctx context.Context
defaultWq,
crdHealthMap,
orkHealth,
kfg.Cluster().DefaultWorkers,
kfg.Katalog().DefaultWorkers,
katalog.NewDependencyGraph(kat),
kfg.Cluster().ShutdownTimeout,
kfg.Katalog().ShutdownTimeout,
)

// ── 7. Komponent list ─────────────────────────────────────────────────────
Expand All @@ -577,7 +578,7 @@ func konstructOrkestra(kfg *konfig.Konfig, m *merger.Merger, ctx context.Context
// On OS signal (SIGTERM/SIGINT) or fatal error, calls Stop() in reverse.
// Graceful shutdown: drains queues before stopping workers.
o := ork.NewOrkestra(
kfg.Cluster().ShutdownGracePeriod,
kfg.Katalog().ShutdownGracePeriod,
kfg.Ork().LogLevel,
)
o.Register(komponents)
Expand Down
Loading
Loading