Files
pulse/internal/monitoring/metrics_target_resolution.go
T
rcourtman 8823440f33 Stop mock-mode callers that read identity rebuilding the estate view
In mock mode the unified view is keyed on the fixture data version, which
advances on every metric tick, and building it for the default
1,776-resource estate costs about half a second of CPU (many seconds on a
starved process). #2778 moved the metrics-target lookup onto the fixture
structure revision; the other callers that read only what the estate
lists and how it connects still rebuilt the whole view after each tick.
At startup they made 42 view requests, all misses, and with no client
connected the monitor tick alone rebuilt the estate about every nine
seconds.

Callers that read identity and topology now share the view built for the
current structure revision and operator links (currentStructureUnifiedStateView,
GetUnifiedStructureReadState): the Docker-alert prune, the workload chart
prewarm, the demo Patrol resolver, and the host-agent evaluation, which
also lists nodes only when an offline host names a linked node. The
connection-degraded alert feed keeps only platform rows, so it no longer
reads the hosts, their desired-config fingerprints or the PBS instances
that only feed agent rows and a host alias its snapshot drops.

The mock storage chart is not one of them: a linked pool takes its
capacity from whichever source was seen last, which a tick can move
without a structure change. It keeps the data-version view, read once per
request instead of once per uncached pool. The per-node polling lookups
of a monitor that keeps real polling in mock mode (linkedHostForNode,
getHostAgentTemperatureForNode, carriedTemperatureOutlivesAgentLease)
read a host's status, disks and sensors, which a tick moves, so they stay
on the data-version view.

On pulse-dev (one pinned core, host load 30 to 110, probe builds): a
start built the estate view twice before the listener answered with the
wiring change alone and once with this; with no client connected the
process built the estate 33 times in 299 s and used 12.3 s of CPU over a
180 s window, against twice and 3.3 s with this change.
2026-10-10 12:37:42 +01:00

52 lines
2.7 KiB
Go

package monitoring
import (
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
// mockMetricsTarget resolves a canonical resource ID to its metrics-store
// target in mock mode. The second result is false when mock mode is off, so
// the caller takes its ordinary path; when it is true the answer is final for
// this structure, a nil target meaning the view captured no metrics target for
// the ID (the estate does not list it, or lists a resource that has none, such
// as a VMware network); the caller still tries the raw resource store.
//
// Alert evaluation resolves a target for every resource and every windowed
// metric (CPU by default) on every pass, through MetricsTargetForResource.
// The mock view that answers it is cached per fixture data version, and that
// version advances on every metric tick (2 s by default). Rebuilding the view
// ingests the whole estate twice (the memoized fixture snapshot, then the
// view's own registry), about half a second of CPU for the default estate
// (1,776 resources) on an idle core. A build that takes longer than one tick publishes
// its view under an already-stale version, so the next lookup rebuilds again:
// on a host with CPU to spare that costs one build per tick, but when the
// process is starved (a loaded dev VM, a small demo host) the cache never
// hits, every lookup in the pass builds an estate, and the monitor never
// finishes its first evaluation. Startup stalled in exactly that state,
// before the listener opened, with the watchdog dump parked in the view build
// beneath evaluateMetricWindow.
//
// A metrics target depends only on identity inputs: a resource's type, its
// source mappings, and the technology, Docker container ID and disk identity
// of its facets (unifiedresources.BuildMetricsTarget), falling back to the
// resource's own MetricsTarget. A metric tick moves values and timestamps,
// never those inputs (TestMetricTicksNeverChangeTheFixtureStructure), so the
// captured targets of a view built at any data version of the current
// structure answer the lookup. A data-version change alone therefore never
// triggers a build here: only a structural change (mock mode toggled, the
// fixture reconfigured) or a link change makes the next lookup rebuild the
// view. Two lookups that miss together can each still build one, and a cold
// cache builds even for an ID the estate does not list.
func (m *Monitor) mockMetricsTarget(resourceID string) (*unifiedresources.MetricsTarget, bool) {
if m == nil || !mock.IsMockEnabled() {
return nil, false
}
view := m.currentStructureUnifiedStateView()
if view.metricsTargets == nil {
return nil, true
}
return view.metricsTargets.MetricsTargetForResource(resourceID), true
}