This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4119d9cba691 TUI: load averages, UX improvements, lazy loading,
navigation fixes (#23269)
4119d9cba691 is described below
commit 4119d9cba691c36fa7e1e177603fa8d3aca91015
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun May 17 21:39:36 2026 +0200
TUI: load averages, UX improvements, lazy loading, navigation fixes (#23269)
* 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]>
* TUI: fix overview selection bugs - sort consistency and inflight load
from JSON
- Extract sortedOverviewInfos() so renderOverview,
syncSelectedPidFromOverview,
selectCurrentIntegration, and navigateDown all use the same sorted order;
previously UP/DOWN keys resolved row index against an unsorted list while
the table rendered a sorted one, causing the wrong integration to be
selected
- Read inflight load01/05/15 directly from the management JSON (already
computed
by ManagedCamelContext.LoadTriplet and serialized by ContextDevConsole)
instead
of maintaining a redundant local EWMA; works correctly for remote
processes too
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* TUI: lazy load log/trace/history and move circuit breaker tab last
- Gate log file tailing on Log tab being active; reset file position on
tab switch so the next tick picks up correctly — avoids disk reads on
every tick when on other tabs (was the only reason for the (*) indicator
which has now been removed from the Log tab header)
- Gate trace file reads on Trace tab being active; tab-switch handler
already does an explicit load so tick-level reads were redundant
- Remove eager refreshHistoryData/refreshTraceData from selection-change
handlers; handleTabKey already loads on demand when switching to those
tabs
- Move Circuit Breaker to tab 9 (last); shift Health→6, Last→7, Trace→8
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* TUI: disable cursor navigation in Consumers, Endpoints, Health tabs
Tabs 4, 5, 6 have no detail action yet. Remove highlightStyle and
highlightSpacing(ALWAYS) from the table builders so no selection
highlight appears, and drop the navigateUp/navigateDown cases for
these tabs so cursor keys are inert on them.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* TUI: remove navigate hint from footer for Consumers, Endpoints, Health
tabs
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* TUI: move Esc hint after q in overview footer
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---------
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 280 +++++++++++++--------
1 file changed, 179 insertions(+), 101 deletions(-)
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..0c20ff224d2d 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;
@@ -120,10 +122,10 @@ public class CamelMonitor extends CamelCommand {
private static final int TAB_ROUTES = 2;
private static final int TAB_CONSUMERS = 3;
private static final int TAB_ENDPOINTS = 4;
- private static final int TAB_CIRCUIT_BREAKER = 5;
- private static final int TAB_HEALTH = 6;
- private static final int TAB_HISTORY = 7;
- private static final int TAB_TRACE = 8;
+ private static final int TAB_HEALTH = 5;
+ private static final int TAB_HISTORY = 6;
+ private static final int TAB_TRACE = 7;
+ private static final int TAB_CIRCUIT_BREAKER = 8;
// Overview sort columns
private static final String[] OVERVIEW_SORT_COLUMNS = { "pid", "name",
"version", "status", "total", "fail" };
@@ -182,6 +184,10 @@ 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%, per PID (inflight EWMA is read from the
management JSON)
+ private final Map<String, LoadAvg> cpuLoadAvg = new ConcurrentHashMap<>();
+ private final Map<String, long[]> prevCpuSample = new
ConcurrentHashMap<>();
+
// Overview sort state
private String overviewSort = "name";
private int overviewSortIndex = 1;
@@ -204,6 +210,7 @@ public class CamelMonitor extends CamelCommand {
// Endpoint filter state
private boolean showOnlyRemote;
+ private boolean showEndpointChart = true;
// Circuit breaker sort state (default: route = index 0)
private String cbSort = "route";
@@ -268,7 +275,10 @@ public class CamelMonitor extends CamelCommand {
private String selectedPid;
// Diagram state
- private boolean chartAllIntegrations = true;
+ private static final int CHART_ALL = 0;
+ private static final int CHART_SINGLE = 1;
+ private static final int CHART_OFF = 2;
+ private int chartMode = CHART_ALL;
private boolean showDiagram;
private boolean diagramTextMode;
private boolean diagramMetrics = true;
@@ -397,16 +407,16 @@ public class CamelMonitor extends CamelCommand {
return handleTabKey(TAB_ENDPOINTS);
}
if (ke.isChar('6')) {
- return handleTabKey(TAB_CIRCUIT_BREAKER);
+ return handleTabKey(TAB_HEALTH);
}
if (ke.isChar('7')) {
- return handleTabKey(TAB_HEALTH);
+ return handleTabKey(TAB_HISTORY);
}
if (ke.isChar('8')) {
- return handleTabKey(TAB_HISTORY);
+ return handleTabKey(TAB_TRACE);
}
if (ke.isChar('9')) {
- return handleTabKey(TAB_TRACE);
+ return handleTabKey(TAB_CIRCUIT_BREAKER);
}
// Tab cycling
@@ -526,9 +536,9 @@ public class CamelMonitor extends CamelCommand {
overviewSortReversed = !overviewSortReversed;
return true;
}
- // Overview tab: toggle chart between all integrations and
selected only
+ // Overview tab: cycle chart between all integrations, selected
only, and off
if (tab == TAB_OVERVIEW && ke.isCharIgnoreCase('a')) {
- chartAllIntegrations = !chartAllIntegrations;
+ chartMode = (chartMode + 1) % 3;
return true;
}
// Overview tab: start/stop all routes for selected integration
@@ -580,6 +590,10 @@ public class CamelMonitor extends CamelCommand {
showOnlyRemote = !showOnlyRemote;
return true;
}
+ if (tab == TAB_ENDPOINTS && ke.isCharIgnoreCase('a')) {
+ showEndpointChart = !showEndpointChart;
+ return true;
+ }
// Routes tab: sort and diagram
if (tab == TAB_ROUTES && ke.isChar('s')) {
@@ -840,6 +854,11 @@ public class CamelMonitor extends CamelCommand {
if (tab != TAB_OVERVIEW) {
selectCurrentIntegration();
}
+ if (tab == TAB_LOG) {
+ // Reset log state so the next tick tails from the correct
file/position
+ logFilePos = -1;
+ logLineBuffer.setLength(0);
+ }
if (tab == TAB_HISTORY && selectedPid != null) {
refreshHistoryData(List.of(Long.parseLong(selectedPid)));
if (!historyEntries.isEmpty()) {
@@ -861,28 +880,29 @@ public class CamelMonitor extends CamelCommand {
return true;
}
+ // Returns integrations in the same order the overview table renders them.
+ // Must be used anywhere that translates a table row index to a PID.
+ private List<IntegrationInfo> sortedOverviewInfos() {
+ List<IntegrationInfo> infos = new ArrayList<>(data.get());
+ infos.sort(this::sortOverview);
+ return infos;
+ }
+
private void selectCurrentIntegration() {
if (selectedPid != null) {
return;
}
- List<IntegrationInfo> infos = data.get().stream().filter(i ->
!i.vanishing).toList();
+ List<IntegrationInfo> infos = sortedOverviewInfos();
Integer sel = overviewTableState.selected();
if (sel != null && sel >= 0 && sel < infos.size()) {
selectedPid = infos.get(sel).pid;
} else if (infos.size() == 1) {
selectedPid = infos.get(0).pid;
}
- if (selectedPid != null) {
- List<Long> pids = List.of(Long.parseLong(selectedPid));
- refreshHistoryData(pids);
- traceFilePositions.clear();
- traces.set(Collections.emptyList());
- refreshTraceData(pids);
- }
}
private void syncSelectedPidFromOverview() {
- List<IntegrationInfo> infos = data.get().stream().filter(i ->
!i.vanishing).toList();
+ List<IntegrationInfo> infos = sortedOverviewInfos();
Integer sel = overviewTableState.selected();
String newPid = null;
if (sel != null && sel >= 0 && sel < infos.size()) {
@@ -893,11 +913,6 @@ public class CamelMonitor extends CamelCommand {
if (newPid != null && !newPid.equals(selectedPid)) {
selectedPid = newPid;
resetIntegrationTabState();
- List<Long> pids = List.of(Long.parseLong(selectedPid));
- refreshHistoryData(pids);
- traceFilePositions.clear();
- traces.set(Collections.emptyList());
- refreshTraceData(pids);
}
}
@@ -943,9 +958,6 @@ public class CamelMonitor extends CamelCommand {
syncSelectedPidFromOverview();
}
case TAB_ROUTES -> routeTableState.selectPrevious();
- case TAB_CONSUMERS -> consumerTableState.selectPrevious();
- case TAB_HEALTH -> healthTableState.selectPrevious();
- case TAB_ENDPOINTS -> endpointTableState.selectPrevious();
case TAB_CIRCUIT_BREAKER -> cbTableState.selectPrevious();
case TAB_LOG -> {
logFollowMode = false;
@@ -970,25 +982,13 @@ public class CamelMonitor extends CamelCommand {
List<IntegrationInfo> infos = data.get().stream().filter(i ->
!i.vanishing).toList();
switch (tabsState.selected()) {
case TAB_OVERVIEW -> {
- overviewTableState.selectNext(infos.size());
+ overviewTableState.selectNext(sortedOverviewInfos().size());
syncSelectedPidFromOverview();
}
case TAB_ROUTES -> {
IntegrationInfo info = findSelectedIntegration();
routeTableState.selectNext(info != null ? info.routes.size() :
0);
}
- case TAB_CONSUMERS -> {
- IntegrationInfo info = findSelectedIntegration();
- consumerTableState.selectNext(info != null ?
info.consumers.size() : 0);
- }
- case TAB_HEALTH -> {
- IntegrationInfo info = findSelectedIntegration();
- healthTableState.selectNext(info != null ?
getFilteredHealthChecks(info).size() : 0);
- }
- case TAB_ENDPOINTS -> {
- IntegrationInfo info = findSelectedIntegration();
- endpointTableState.selectNext(info != null ?
info.endpoints.size() : 0);
- }
case TAB_CIRCUIT_BREAKER -> {
IntegrationInfo info = findSelectedIntegration();
cbTableState.selectNext(info != null ?
info.circuitBreakers.size() : 0);
@@ -1080,20 +1080,17 @@ public class CamelMonitor extends CamelCommand {
Tabs tabs = Tabs.builder()
.titles(
badge(" 1 Overview ", activeCount),
- filteredLogEntries.isEmpty()
- ? Line.from(" 2 Log ")
- : Line.from(Span.raw(" 2 Log "),
Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()),
- Span.raw(" ")),
+ Line.from(" 2 Log "),
badge(" 3 Routes ", routeCount),
badge(" 4 Consumers ", consumerCount),
badge(" 5 Endpoints ", endpointCount),
- badgeCb(" 6 Circuit Breaker ", cbCount, cbOpenCount),
- badgeHealth(" 7 Health ", healthCount,
healthDownCount),
- badge(" 8 Last ", historyCount),
+ badgeHealth(" 6 Health ", healthCount,
healthDownCount),
+ badge(" 7 Last ", historyCount),
hasTraces
- ? Line.from(Span.raw(" 9 Trace "),
Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()),
+ ? Line.from(Span.raw(" 8 Trace "),
Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()),
Span.raw(" "))
- : Line.from(" 9 Trace "))
+ : Line.from(" 8 Trace "),
+ badgeCb(" 9 Circuit Breaker ", cbCount, cbOpenCount))
.highlightStyle(Style.EMPTY.fg(Color.rgb(0xF6, 0x91,
0x23)).bold())
.divider(Span.styled(" | ", Style.EMPTY.dim()))
.build();
@@ -1122,11 +1119,20 @@ public class CamelMonitor extends CamelCommand {
// ---- Tab 1: Overview ----
private void renderOverview(Frame frame, Rect area) {
- List<IntegrationInfo> infos = new ArrayList<>(data.get());
- infos.sort(this::sortOverview);
+ List<IntegrationInfo> infos = sortedOverviewInfos();
+
+ // Keep the table selection index tracking the same PID across sort
changes and data refreshes
+ if (selectedPid != null) {
+ for (int i = 0; i < infos.size(); i++) {
+ if (selectedPid.equals(infos.get(i).pid)) {
+ overviewTableState.select(i);
+ break;
+ }
+ }
+ }
- // Split: table (fill) + chart (14 rows: 13 chart + 1 x-axis) if we
have data
- boolean hasSparkline = !throughputHistory.isEmpty();
+ // Split: table (fill) + chart (14 rows: 13 chart + 1 x-axis) if we
have data and chart is on
+ boolean hasSparkline = chartMode != CHART_OFF &&
!throughputHistory.isEmpty();
List<Rect> chunks;
if (hasSparkline) {
chunks = Layout.vertical()
@@ -1257,7 +1263,7 @@ public class CamelMonitor extends CamelCommand {
// Merge throughput histories: all PIDs or selected only
long[] mergedTotal = new long[renderPoints];
long[] mergedFailed = new long[renderPoints];
- String chartPid = (!chartAllIntegrations && selectedPid != null) ?
selectedPid : null;
+ String chartPid = (chartMode == CHART_SINGLE && selectedPid !=
null) ? selectedPid : null;
for (int i = 0; i < renderPoints; i++) {
for (Map.Entry<String, LinkedList<Long>> e :
throughputHistory.entrySet()) {
if (chartPid == null || chartPid.equals(e.getKey())) {
@@ -1287,7 +1293,7 @@ public class CamelMonitor extends CamelCommand {
// Styled legend in chart title
Line titleLine;
- if (!chartAllIntegrations && selectedPid != null) {
+ if (chartMode == CHART_SINGLE && selectedPid != null) {
IntegrationInfo chartSel = findSelectedIntegration();
String chartName = chartSel != null ?
TuiHelper.truncate(chartSel.name, 12) : selectedPid;
titleLine = Line.from(
@@ -1456,6 +1462,22 @@ public class CamelMonitor extends CamelCommand {
Span.styled("Thds: ", dim),
Span.raw(sel.threadCount + " / " +
sel.peakThreadCount)));
}
+ LoadAvg cpu = cpuLoadAvg.get(sel.pid);
+ boolean hasInfl = sel.inflightLoad01 != null &&
!sel.inflightLoad01.isEmpty();
+ if (cpu != null || hasInfl) {
+ 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 (hasInfl) {
+ lines.add(Line.from(
+ Span.styled("Infl: ", dim),
+ Span.raw(sel.inflightLoad01 + " / " +
sel.inflightLoad05 + " / " + sel.inflightLoad15)));
+ }
+ }
} else {
lines.add(Line.from(Span.raw("-")));
}
@@ -1823,8 +1845,6 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(10),
Constraint.length(22),
Constraint.fill())
- .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
- .highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Consumers sort:" + consumerSort + "
").build())
.build();
@@ -2888,8 +2908,6 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(12),
Constraint.length(6),
Constraint.fill())
- .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
- .highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
.build();
@@ -2971,27 +2989,27 @@ public class CamelMonitor extends CamelCommand {
Constraint.length(6),
Constraint.length(8),
Constraint.fill())
- .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
- .highlightSpacing(Table.HighlightSpacing.ALWAYS)
.block(Block.builder().borderType(BorderType.ROUNDED)
.title(" Endpoints sort:" + endpointSort +
(showOnlyRemote ? " remote" : "") + " ").build())
.build();
- List<Rect> chunks = Layout.vertical()
- .constraints(Constraint.fill(), Constraint.length(12))
- .split(area);
+ List<Rect> chunks = showEndpointChart
+ ? Layout.vertical().constraints(Constraint.fill(),
Constraint.length(16)).split(area)
+ : List.of(area);
frame.renderStatefulWidget(table, chunks.get(0), endpointTableState);
- long inTotal = info.endpoints.stream()
- .filter(ep -> "in".equals(ep.direction) && (!showOnlyRemote ||
ep.remote))
- .mapToLong(ep -> ep.hits)
- .sum();
- long outTotal = info.endpoints.stream()
- .filter(ep -> "out".equals(ep.direction) && (!showOnlyRemote
|| ep.remote))
- .mapToLong(ep -> ep.hits)
- .sum();
- renderEndpointFlow(frame, chunks.get(1), inTotal, outTotal, info.name,
info.pid, showOnlyRemote);
+ if (showEndpointChart) {
+ long inTotal = info.endpoints.stream()
+ .filter(ep -> "in".equals(ep.direction) &&
(!showOnlyRemote || ep.remote))
+ .mapToLong(ep -> ep.hits)
+ .sum();
+ long outTotal = info.endpoints.stream()
+ .filter(ep -> "out".equals(ep.direction) &&
(!showOnlyRemote || ep.remote))
+ .mapToLong(ep -> ep.hits)
+ .sum();
+ renderEndpointFlow(frame, chunks.get(1), inTotal, outTotal,
info.name, info.pid, showOnlyRemote);
+ }
}
private void renderEndpointFlow(
@@ -3878,16 +3896,22 @@ public class CamelMonitor extends CamelCommand {
if (tab == TAB_OVERVIEW) {
hint(spans, "q", "quit");
+ if (selectedPid != null) {
+ hint(spans, "Esc", "unselect");
+ }
hint(spans, "\u2191\u2193", "navigate");
hint(spans, "s", "sort");
- hint(spans, "a", "chart " + (chartAllIntegrations ? "[all]" :
"[single]"));
+ hint(spans, "a", "chart " + switch (chartMode) {
+ case CHART_ALL -> "[all]";
+ case CHART_SINGLE -> "[single]";
+ default -> "[off]";
+ });
hint(spans, "Enter", "details");
if (selectedPid != null) {
IntegrationInfo selInfo = findSelectedIntegration();
if (selInfo != null) {
hint(spans, "p", selInfo.routeStarted > 0 ? "stop" :
"start");
}
- hint(spans, "Esc", "unselect");
}
hint(spans, "1-9", "tabs");
} else if (tab == TAB_ROUTES && showSource) {
@@ -3932,14 +3956,13 @@ public class CamelMonitor extends CamelCommand {
hint(spans, "1-9", "tabs");
} else if (tab == TAB_CONSUMERS) {
hint(spans, "Esc", "back");
- hint(spans, "\u2191\u2193", "navigate");
hint(spans, "s", "sort");
hint(spans, "1-9", "tabs");
} else if (tab == TAB_ENDPOINTS) {
hint(spans, "Esc", "back");
- hint(spans, "\u2191\u2193", "navigate");
hint(spans, "s", "sort");
hint(spans, "r", "remote" + (showOnlyRemote ? " [on]" : " [off]"));
+ hint(spans, "a", "chart " + (showEndpointChart ? "[all]" :
"[off]"));
hint(spans, "1-9", "tabs");
} else if (tab == TAB_CIRCUIT_BREAKER) {
hint(spans, "Esc", "back");
@@ -3948,7 +3971,6 @@ public class CamelMonitor extends CamelCommand {
hint(spans, "1-9", "tabs");
} else if (tab == TAB_HEALTH) {
hint(spans, "Esc", "back");
- hint(spans, "\u2191\u2193", "navigate");
hint(spans, "d", "toggle DOWN");
hint(spans, "1-9", "tabs");
} else if (tab == TAB_LOG && showLogLevelPopup) {
@@ -4110,6 +4132,7 @@ public class CamelMonitor extends CamelCommand {
infos.add(info);
updateThroughputHistory(info);
updateEndpointHistory(info);
+ updateLoadMetrics(ph, info);
}
}
});
@@ -4140,6 +4163,8 @@ public class CamelMonitor extends CamelCommand {
endpointRemoteOutHistory.remove(entry.getKey());
endpointRemoteSamples.remove(entry.getKey());
previousEndpointRemoteTime.remove(entry.getKey());
+ cpuLoadAvg.remove(entry.getKey());
+ prevCpuSample.remove(entry.getKey());
} else if (!livePids.contains(entry.getKey())) {
IntegrationInfo ghost = entry.getValue().info;
ghost.vanishing = true;
@@ -4152,33 +4177,37 @@ public class CamelMonitor extends CamelCommand {
data.set(infos);
- // Refresh log data for the selected integration (incremental tail)
- IntegrationInfo selected = findSelectedIntegration();
- if (selected != null) {
- if (!selected.pid.equals(logFilePid)) {
- // Integration changed: reset all incremental log state
- mutableFilteredEntries.clear();
- logFilePos = -1;
- logTotalLinesRead = 0;
- logEvictedSeen = 0;
- logLineBuffer.setLength(0);
- }
- List<String> newRawLines = new ArrayList<>();
- readNewLogLines(selected.pid, newRawLines);
- if (!newRawLines.isEmpty()) {
- logTotalLinesRead += newRawLines.size();
- for (String line : newRawLines) {
- mutableFilteredEntries.add(parseLogLine(line));
+ // Refresh log data only when the Log tab is visible
+ if (tabsState.selected() == TAB_LOG) {
+ IntegrationInfo selected = findSelectedIntegration();
+ if (selected != null) {
+ if (!selected.pid.equals(logFilePid)) {
+ // Integration changed: reset all incremental log state
+ mutableFilteredEntries.clear();
+ logFilePos = -1;
+ logTotalLinesRead = 0;
+ logEvictedSeen = 0;
+ logLineBuffer.setLength(0);
}
- if (mutableFilteredEntries.size() > MAX_LOG_LINES) {
- mutableFilteredEntries.subList(0,
mutableFilteredEntries.size() - MAX_LOG_LINES).clear();
+ List<String> newRawLines = new ArrayList<>();
+ readNewLogLines(selected.pid, newRawLines);
+ if (!newRawLines.isEmpty()) {
+ logTotalLinesRead += newRawLines.size();
+ for (String line : newRawLines) {
+ mutableFilteredEntries.add(parseLogLine(line));
+ }
+ if (mutableFilteredEntries.size() > MAX_LOG_LINES) {
+ mutableFilteredEntries.subList(0,
mutableFilteredEntries.size() - MAX_LOG_LINES).clear();
+ }
+ filteredLogEntries = new
ArrayList<>(mutableFilteredEntries);
}
- filteredLogEntries = new
ArrayList<>(mutableFilteredEntries);
}
}
- // Refresh trace data
- refreshTraceData(pids);
+ // Refresh trace data only when the Trace tab is visible
+ if (tabsState.selected() == TAB_TRACE) {
+ refreshTraceData(pids);
+ }
} catch (Exception e) {
// ignore refresh errors
}
@@ -4313,6 +4342,27 @@ public class CamelMonitor extends CamelCommand {
traces.set(allTraces);
}
+ private void updateLoadMetrics(ProcessHandle ph, IntegrationInfo info) {
+ String pid = info.pid;
+
+ // 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");
@@ -4742,6 +4792,9 @@ public class CamelMonitor extends CamelCommand {
info.exchangesTotal = objToLong(stats.get("exchangesTotal"));
info.failed = objToLong(stats.get("exchangesFailed"));
info.inflight = objToLong(stats.get("exchangesInflight"));
+ info.inflightLoad01 = objToString(stats.get("load01"));
+ info.inflightLoad05 = objToString(stats.get("load05"));
+ info.inflightLoad15 = objToString(stats.get("load15"));
info.last = objToString(stats.get("lastProcessingTime"));
info.delta = objToString(stats.get("deltaProcessingTime"));
long tsStarted =
objToLong(stats.get("lastCreatedExchangeTimestamp"));
@@ -5070,6 +5123,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 {
@@ -5091,6 +5166,9 @@ public class CamelMonitor extends CamelCommand {
long exchangesTotal;
long failed;
long inflight;
+ String inflightLoad01;
+ String inflightLoad05;
+ String inflightLoad15;
String last;
String delta;
String sinceLastStarted;