This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/camel-tui-load-avg in repository https://gitbox.apache.org/repos/asf/camel.git
commit 327985259ca5a7217697780143ac386fd8abae88 Author: Claus Ibsen <[email protected]> AuthorDate: Sun May 17 19:34:54 2026 +0200 TUI: add CPU and inflight EWMA load averages to info panel Displays 1m/5m/15m exponentially weighted moving averages for both process CPU% (sampled via ProcessHandle.totalCpuDuration delta) and inflight exchange concurrency in the overview info panel, mirroring the Unix uptime load average convention used by Camel's own LoadTriplet. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java index ab3a259ca40d..5cc05f6bf56b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java @@ -21,6 +21,7 @@ import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; @@ -31,6 +32,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -182,6 +184,11 @@ public class CamelMonitor extends CamelCommand { private final Map<String, LinkedList<long[]>> endpointRemoteSamples = new ConcurrentHashMap<>(); private final Map<String, Long> previousEndpointRemoteTime = new ConcurrentHashMap<>(); + // Load averages (EWMA) — CPU% and inflight exchanges, per PID + private final Map<String, LoadAvg> cpuLoadAvg = new ConcurrentHashMap<>(); + private final Map<String, LoadAvg> inflightLoadAvg = new ConcurrentHashMap<>(); + private final Map<String, long[]> prevCpuSample = new ConcurrentHashMap<>(); + // Overview sort state private String overviewSort = "name"; private int overviewSortIndex = 1; @@ -1456,6 +1463,22 @@ public class CamelMonitor extends CamelCommand { Span.styled("Thds: ", dim), Span.raw(sel.threadCount + " / " + sel.peakThreadCount))); } + LoadAvg cpu = cpuLoadAvg.get(sel.pid); + LoadAvg infl = inflightLoadAvg.get(sel.pid); + if (cpu != null || infl != null) { + lines.add(Line.from(Span.raw(""))); + lines.add(Line.from(Span.styled("Load (1m/5m/15m):", dim))); + if (cpu != null) { + lines.add(Line.from( + Span.styled("CPU: ", dim), + Span.raw(cpu.format("%.1f / %.1f / %.1f %%")))); + } + if (infl != null) { + lines.add(Line.from( + Span.styled("Infl: ", dim), + Span.raw(infl.format("%.1f / %.1f / %.1f")))); + } + } } else { lines.add(Line.from(Span.raw("-"))); } @@ -4110,6 +4133,7 @@ public class CamelMonitor extends CamelCommand { infos.add(info); updateThroughputHistory(info); updateEndpointHistory(info); + updateLoadMetrics(ph, info); } } }); @@ -4140,6 +4164,9 @@ public class CamelMonitor extends CamelCommand { endpointRemoteOutHistory.remove(entry.getKey()); endpointRemoteSamples.remove(entry.getKey()); previousEndpointRemoteTime.remove(entry.getKey()); + cpuLoadAvg.remove(entry.getKey()); + inflightLoadAvg.remove(entry.getKey()); + prevCpuSample.remove(entry.getKey()); } else if (!livePids.contains(entry.getKey())) { IntegrationInfo ghost = entry.getValue().info; ghost.vanishing = true; @@ -4313,6 +4340,30 @@ public class CamelMonitor extends CamelCommand { traces.set(allTraces); } + private void updateLoadMetrics(ProcessHandle ph, IntegrationInfo info) { + String pid = info.pid; + + // Inflight EWMA — feed current inflight count directly + inflightLoadAvg.computeIfAbsent(pid, k -> new LoadAvg()).update(info.inflight); + + // CPU EWMA — compute % from ProcessHandle CPU duration delta + Optional<Duration> durOpt = ph.info().totalCpuDuration(); + if (durOpt.isPresent()) { + long cpuNanos = durOpt.get().toNanos(); + long wallMs = System.currentTimeMillis(); + long[] prev = prevCpuSample.get(pid); + if (prev != null) { + long deltaCpuNanos = cpuNanos - prev[0]; + long deltaWallNanos = (wallMs - prev[1]) * 1_000_000L; + if (deltaWallNanos > 0) { + double cpuPct = (double) deltaCpuNanos / deltaWallNanos * 100.0; + cpuLoadAvg.computeIfAbsent(pid, k -> new LoadAvg()).update(Math.max(0, cpuPct)); + } + } + prevCpuSample.put(pid, new long[] { cpuNanos, wallMs }); + } + } + @SuppressWarnings("unchecked") private void readTraceFile(String pid, List<TraceEntry> allTraces) { Path traceFile = CommandLineHelper.getCamelDir().resolve(pid + "-trace.json"); @@ -5070,6 +5121,28 @@ public class CamelMonitor extends CamelCommand { return TuiHelper.objToLong(o); } + // ---- Load Average ---- + + private static class LoadAvg { + private static final double EXP_1 = Math.exp(-1 / 60.0); + private static final double EXP_5 = Math.exp(-1 / (60.0 * 5.0)); + private static final double EXP_15 = Math.exp(-1 / (60.0 * 15.0)); + + private double load1 = Double.NaN; + private double load5 = Double.NaN; + private double load15 = Double.NaN; + + synchronized void update(double value) { + load1 = Double.isNaN(load1) ? value : value + EXP_1 * (load1 - value); + load5 = Double.isNaN(load5) ? value : value + EXP_5 * (load5 - value); + load15 = Double.isNaN(load15) ? value : value + EXP_15 * (load15 - value); + } + + synchronized String format(String fmt) { + return Double.isNaN(load1) ? "-" : String.format(fmt, load1, load5, load15); + } + } + // ---- Data Classes ---- static class IntegrationInfo {
