TargetHeapControllerResolver.java
package io.github.baokhang83.mnemo.warden.agent.heap;
import io.github.baokhang83.mnemo.warden.agent.AgentLog;
import io.github.baokhang83.mnemo.warden.agent.attach.AttachedJvm;
import io.github.baokhang83.mnemo.warden.agent.metrics.AgentMetrics;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
/**
* Resolves an attached target's {@link HeapController} once per attach and caches the verdict
* — including a permanently-unsupported collector — keyed on {@link AttachedJvm}
* reference identity.
*
* <p>Construction resolves the target's cgroup directory via a bounded-depth filesystem walk
* ({@code RssReader.resolveCgroupRoot}) — expensive enough that redoing it every poll tick
* just to sample a gauge would violate the agent's lean-footprint principle (constitution
* §4). An unsupported collector ({@link UnsupportedCollectorException} on construction) is a
* permanent fact about the attach, not a transient failure, so it is resolved and logged exactly
* once here — not re-attempted and re-logged on every tick — and recorded on {@link
* AgentMetrics} so it's visible on {@code /metrics} (W-603).
*
* <p>Lives in this package (rather than alongside its one caller, {@code IntentWatcher}) so it
* can reach {@link AttachedHeapController}'s package-private {@code forTarget(AttachedJvm, Path)}
* seam — the same one {@code AttachedHeapControllerTest} uses to point cgroup resolution at
* a real {@code /sys/fs/cgroup} on CI, which runs the target directly on the runner rather than in
* a pod with the {@code hostPath} mount.
*/
public final class TargetHeapControllerResolver {
private final AgentMetrics metrics;
private AttachedJvm cachedTargetJvm;
private Optional<HeapController> cachedHeap = Optional.empty();
public TargetHeapControllerResolver(AgentMetrics metrics) {
this.metrics = metrics;
}
/**
* @return the target's {@link HeapController}, or empty if its collector doesn't support Warden
* at all
*/
public Optional<HeapController> resolve(AttachedJvm target) throws IOException {
return resolve(target, Path.of(RssReader.HOST_CGROUP_ROOT));
}
/**
* Package-private seam so tests (and CI) can point cgroup resolution at a real {@code
* /sys/fs/cgroup} instead of the deployment-only {@link RssReader#HOST_CGROUP_ROOT} —
* mirrors exactly how {@code AttachedHeapControllerTest} exercises {@link
* AttachedHeapController} itself.
*/
Optional<HeapController> resolve(AttachedJvm target, Path hostCgroupRoot) throws IOException {
if (cachedTargetJvm != target) {
cachedTargetJvm = target;
try {
HeapController heap = AttachedHeapController.forTarget(target, hostCgroupRoot);
cachedHeap = Optional.of(heap);
metrics.setGcSupported(heap.capabilities().collector().toString(), true);
} catch (UnsupportedCollectorException e) {
cachedHeap = Optional.empty();
metrics.setGcSupported(e.actual().toString(), false);
AgentLog.info(
"target's collector (" + e.actual() + ") does not support Warden resize operations;"
+ " agent is read-only for this target");
}
}
return cachedHeap;
}
}