mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-20 14:33:30 +00:00
Bucket agent candidates for the child-count parent pass
The derived-parent recompute in buildChildCounts may hit the agent fallback scan once per guest, and that scan walked every resource with per-entry string checks, an O(n^2) pass per registry ingest. The pass now buckets agent resources by node name up front and the fallback walks only the matching bucket. Selection is order-independent (best score with smallest-ID tie-break) and scores read live through shared pointers, so results are unchanged; callers outside the pass keep the full scan. Contract-Neutral: performance-only scan bucketing in the parent recompute pass, selection semantics unchanged
This commit is contained in:
parent
81de801e58
commit
d18fcf55ad
1 changed files with 52 additions and 6 deletions
|
|
@ -125,6 +125,21 @@ type ResourceRegistry struct {
|
|||
// scan over every mapping. Rebuilt with the views; valid only while
|
||||
// viewsDirty is false, like every other cached field above.
|
||||
cachedSourceTargets map[string][]SourceTarget
|
||||
|
||||
// agentNodeScanIndex buckets agent resources by lowercased node name for
|
||||
// the duration of one buildChildCounts pass, where the agent-parent
|
||||
// fallback would otherwise scan every resource once per guest. Only that
|
||||
// pass sets it; nil means callers take the full scan. Bucket membership
|
||||
// is fixed for the pass (Type and NodeName are never mutated mid-pass)
|
||||
// while scores stay live through the shared pointers.
|
||||
agentNodeScanIndex map[string][]agentNodeCandidate
|
||||
}
|
||||
|
||||
// agentNodeCandidate pairs a resources-map key with its entry so the indexed
|
||||
// fallback scan sees exactly what the full scan's iteration sees.
|
||||
type agentNodeCandidate struct {
|
||||
id string
|
||||
resource *Resource
|
||||
}
|
||||
|
||||
// NewRegistry creates a new registry using the provided store for overrides.
|
||||
|
|
@ -4385,27 +4400,30 @@ func (rr *ResourceRegistry) proxmoxNodeParentIDFromResourcesLocked(instance, clu
|
|||
excludeID = CanonicalResourceID(strings.TrimSpace(excludeID))
|
||||
bestID := ""
|
||||
bestScore := -1
|
||||
for id, resource := range rr.resources {
|
||||
|
||||
// Selection is order-independent (best score, smallest ID tie-break), so
|
||||
// walking a node-name bucket equals walking every resource.
|
||||
consider := func(id string, resource *Resource) {
|
||||
if resource == nil || resource.Proxmox == nil {
|
||||
continue
|
||||
return
|
||||
}
|
||||
// Type and node-name checks run before the ID canonicalization:
|
||||
// in a large estate almost every entry is a guest, and this scan
|
||||
// runs per guest, so per-entry string work here is the difference
|
||||
// between a cheap pass and an O(n^2) allocation storm.
|
||||
if CanonicalResourceType(resource.Type) != ResourceTypeAgent {
|
||||
continue
|
||||
return
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(resource.Proxmox.NodeName), nodeName) {
|
||||
continue
|
||||
return
|
||||
}
|
||||
candidateID := CanonicalResourceID(strings.TrimSpace(id))
|
||||
if candidateID == "" || candidateID == excludeID {
|
||||
continue
|
||||
return
|
||||
}
|
||||
score := proxmoxNodeParentScopeScore(instance, clusterName, resource.Proxmox)
|
||||
if score < 0 {
|
||||
continue
|
||||
return
|
||||
}
|
||||
score += proxmoxNodeParentAgentScore(resource)
|
||||
if score > bestScore || (score == bestScore && (bestID == "" || candidateID < bestID)) {
|
||||
|
|
@ -4413,6 +4431,18 @@ func (rr *ResourceRegistry) proxmoxNodeParentIDFromResourcesLocked(instance, clu
|
|||
bestScore = score
|
||||
}
|
||||
}
|
||||
|
||||
if rr.agentNodeScanIndex != nil {
|
||||
// buildChildCounts is mid-pass: only this node's agent candidates
|
||||
// can match, so skip the full walk.
|
||||
for _, candidate := range rr.agentNodeScanIndex[strings.ToLower(nodeName)] {
|
||||
consider(candidate.id, candidate.resource)
|
||||
}
|
||||
return bestID
|
||||
}
|
||||
for id, resource := range rr.resources {
|
||||
consider(id, resource)
|
||||
}
|
||||
return bestID
|
||||
}
|
||||
|
||||
|
|
@ -4459,6 +4489,22 @@ func proxmoxNodeParentScopeScore(instance, clusterName string, parent *ProxmoxDa
|
|||
}
|
||||
|
||||
func (rr *ResourceRegistry) buildChildCounts() {
|
||||
// The parent-resolution loop below may hit the agent fallback scan once
|
||||
// per guest. Bucketing agents by node name up front turns each of those
|
||||
// scans into a lookup over that node's few candidates.
|
||||
rr.agentNodeScanIndex = make(map[string][]agentNodeCandidate)
|
||||
for id, r := range rr.resources {
|
||||
if r == nil || r.Proxmox == nil || CanonicalResourceType(r.Type) != ResourceTypeAgent {
|
||||
continue
|
||||
}
|
||||
nodeName := strings.ToLower(strings.TrimSpace(r.Proxmox.NodeName))
|
||||
if nodeName == "" {
|
||||
continue
|
||||
}
|
||||
rr.agentNodeScanIndex[nodeName] = append(rr.agentNodeScanIndex[nodeName], agentNodeCandidate{id: id, resource: r})
|
||||
}
|
||||
defer func() { rr.agentNodeScanIndex = nil }()
|
||||
|
||||
// ChildCount and ParentName are derived fields. Clear prior values before
|
||||
// recomputing to prevent stale state after re-parenting or parent removal.
|
||||
for _, r := range rr.resources {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue