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 5edbf4cd5f68 chore: camel-jbang - Process shows each argument after 
the jar, Source Info is about the open file, the heap chart has an axis, and a 
millisecond of jitter is no spike (#29495)
5edbf4cd5f68 is described below

commit 5edbf4cd5f682a14cd8bf594e43c9d1c74b1eabe
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 23:18:50 2026 +0100

    chore: camel-jbang - Process shows each argument after the jar, Source Info 
is about the open file, the heap chart has an axis, and a millisecond of jitter 
is no spike (#29495)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/MemoryTab.java     | 28 ++++++++++++---
 .../dsl/jbang/core/commands/tui/ProcessTab.java    | 31 ++++++++++++----
 .../dsl/jbang/core/commands/tui/SourceTab.java     | 29 +++++++++++----
 .../dsl/jbang/core/commands/tui/TimelineTab.java   | 12 +++++--
 .../core/commands/tui/MemoryTabRenderTest.java     | 41 ++++++++++++++++++++++
 .../core/commands/tui/ProcessTabRenderTest.java    | 18 ++++++++++
 .../core/commands/tui/SourceTabJumpLinksTest.java  | 22 ++++++++++++
 .../core/commands/tui/TimelineTabRenderTest.java   | 11 ++++++
 8 files changed, 173 insertions(+), 19 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
index 06ccd219bd10..1e78cad795b7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
@@ -259,23 +259,34 @@ class MemoryTab extends AbstractTab {
             return;
         }
 
-        // Use committed as the scale ceiling
+        // the top of the chart: the committed heap, or the most used in the 
history when that was more (the heap
+        // shrinks); the axis says which height it is
         long observedMax = 
hist.stream().mapToLong(Long::longValue).max().orElse(1);
         long ceiling = info.heapMemCommitted > 0 ? info.heapMemCommitted : 
observedMax;
         if (observedMax > ceiling) {
             ceiling = observedMax;
         }
 
-        String title = String.format(" Heap Usage (%s / %s committed) ", 
formatBytes(info.heapMemUsed), formatBytes(ceiling));
+        String title = String.format(" Heap Usage (%s used, %s committed) ", 
formatBytes(info.heapMemUsed),
+                formatBytes(info.heapMemCommitted > 0 ? info.heapMemCommitted 
: ceiling));
 
         // Render the block border first
         Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build();
         frame.renderWidget(block, area);
-        Rect inner = block.inner(area);
+        Rect box = block.inner(area);
 
-        if (inner.isEmpty() || ceiling <= 0) {
+        if (box.isEmpty() || ceiling <= 0) {
             return;
         }
+        // the y axis at the left: the top, the half and 0
+        String top = formatBytes(ceiling);
+        String half = formatBytes(ceiling / 2);
+        // the half label can be the wider one: 1G is 512M halfway
+        int axisW = Math.max(top.length(), half.length()) + 1;
+        if (box.width() <= axisW + 10) {
+            axisW = 0;
+        }
+        Rect inner = new Rect(box.x() + axisW, box.y(), box.width() - axisW, 
box.height());
 
         int chartW = inner.width();
         int chartH = inner.height() - 1;
@@ -325,6 +336,15 @@ class MemoryTab extends AbstractTab {
             }
         }
 
+        if (axisW > 0) {
+            Style axis = Style.EMPTY.dim();
+            buf.setString(box.x() + axisW - 1 - top.length(), inner.y(), top, 
axis);
+            if (chartH >= 5) {
+                buf.setString(box.x() + axisW - 1 - half.length(), inner.y() + 
chartH / 2, half, axis);
+            }
+            buf.setString(box.x() + axisW - 2, inner.y() + chartH - 1, "0", 
axis);
+        }
+
         // Render time axis on the bottom row of the inner area (inside the 
block border)
         renderTimeAxis(frame, inner, info, chartW);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
index 6fc5c2cd078d..f5aab1d8d794 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTab.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
@@ -270,16 +271,34 @@ class ProcessTab extends AbstractTab {
         }
     }
 
-    private static List<String> splitCommandLine(String cmdLine) {
+    // options of the JVM whose value is the next token, after which come the 
main class or jar and its arguments
+    private static final Set<String> ONE_VALUE_OPTIONS = Set.of("-jar", "-cp", 
"-classpath", "--class-path");
+
+    /**
+     * The command line a line per option, with its value: an option of the 
JVM that takes one value (-jar, -cp) has
+     * just that one, and the arguments after it (the main class, the files of 
camel run) a line each, so none is cut
+     * off at the end of a long -jar line.
+     */
+    static List<String> splitCommandLine(String cmdLine) {
         List<String> parts = new ArrayList<>();
-        for (String token : cmdLine.split("\\s+")) {
-            if (token.startsWith("-") && !parts.isEmpty()) {
+        boolean oneValue = false;
+        boolean arguments = false;
+        for (String token : cmdLine.trim().split("\\s+")) {
+            if (parts.isEmpty()) {
+                parts.add(token);
+            } else if (token.startsWith("-")) {
                 parts.add(token);
-            } else if (parts.isEmpty()) {
+                oneValue = ONE_VALUE_OPTIONS.contains(token);
+                arguments = false;
+            } else if (oneValue) {
+                parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " " 
+ token);
+                oneValue = false;
+                arguments = true;
+            } else if (arguments) {
                 parts.add(token);
             } else {
-                String last = parts.get(parts.size() - 1);
-                parts.set(parts.size() - 1, last + " " + token);
+                // the values of an option, such as --name foo
+                parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " " 
+ token);
             }
         }
         return parts;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index af9f5b566109..a7e361624029 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -1096,15 +1096,30 @@ class SourceTab extends AbstractTab {
 
     private static final DateTimeFormatter DATE_FMT = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
+    /**
+     * The file the Info panel describes, the one the focus is on: the open 
file when the viewer has the focus, else the
+     * one under the cursor of the list (the open file can be another one, 
opened with g or from another tab).
+     */
+    Path infoPath() {
+        String viewed = sourceViewer.isVisible() && focusOnViewer ? 
sourceViewer.getCurrentFilePath() : null;
+        if (viewed != null) {
+            return Path.of(viewed);
+        }
+        Integer sel = listState.selected();
+        return sel != null && sel < entries.size() ? 
Path.of(entries.get(sel).path()) : null;
+    }
+
     private void renderInfoPanel(Frame frame, Rect area) {
         List<Line> lines = new ArrayList<>();
+        Path path = infoPath();
         Integer sel = listState.selected();
-        if (sel != null && sel < entries.size()) {
-            FilesBrowser.FileEntry entry = entries.get(sel);
-            Path path = Path.of(entry.path());
+        boolean directory = path != null && sel != null && sel < entries.size()
+                && entries.get(sel).directory() && 
path.toString().equals(entries.get(sel).path());
+        if (path != null) {
+            String name = path.getFileName() != null ? 
path.getFileName().toString() : "";
             try {
                 BasicFileAttributes attrs = Files.readAttributes(path, 
BasicFileAttributes.class);
-                if (!entry.directory()) {
+                if (!directory) {
                     lines.add(Line.from(
                             Span.styled(" Size: ", Style.EMPTY.dim()),
                             
Span.raw(FilesBrowser.formatFileSize(attrs.size()))));
@@ -1114,11 +1129,11 @@ class SourceTab extends AbstractTab {
                 lines.add(Line.from(
                         Span.styled(" Modified: ", Style.EMPTY.dim()),
                         Span.raw(modified)));
-                if (!entry.directory()) {
+                if (!directory) {
                     String ext = "";
-                    int dot = entry.name().lastIndexOf('.');
+                    int dot = name.lastIndexOf('.');
                     if (dot > 0) {
-                        ext = entry.name().substring(dot + 1);
+                        ext = name.substring(dot + 1);
                     }
                     if (!ext.isEmpty()) {
                         lines.add(Line.from(
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
index 6afbb89d9f13..2c0f488e1fab 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
@@ -56,6 +56,8 @@ import static 
org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
  */
 class TimelineTab extends AbstractTableTab {
 
+    // the mean time sparkline is at least this many milliseconds high: below 
it, a time is noise
+    static final long MIN_MEAN_SCALE_MILLIS = 10;
     static final int[] WINDOWS = { 60, 120, 300 };
     private static final String BLOCKS = "▁▂▃▄▅▆▇█";
     private static final int CAUSE_ROWS = 4;
@@ -765,8 +767,9 @@ class TimelineTab extends AbstractTableTab {
         }
         List<Span> meanLine = new ArrayList<>();
         meanLine.add(Span.styled(pad("   mean time", label), Theme.label()));
-        // relative to twice the average, so a steady time is a calm line and 
a slow second stands out
-        long scale = n > 0 ? Math.max(maxMean, 2 * sumMean / n) : 0;
+        // relative to twice the average, so a steady time is a calm line and 
a slow second stands out; at least
+        // MIN_MEAN_SCALE_MILLIS, so a millisecond of jitter on a fast route 
is not a full bar
+        long scale = n > 0 ? meanScale(maxMean, sumMean / n) : 0;
         meanLine.add(Span.styled(spark(mean, scale), Theme.info()));
         meanLine.add(Span.raw("  " + (n > 0 ? "avg " + 
ChaosTab.shortTime(sumMean / n) : "no exchange")));
         if (max > 0) {
@@ -875,6 +878,11 @@ class TimelineTab extends AbstractTableTab {
         return lines;
     }
 
+    /** The height of the mean time sparkline: twice the average or the 
slowest second, at least 10 ms. */
+    static long meanScale(long maxMean, long avg) {
+        return Math.max(MIN_MEAN_SCALE_MILLIS, Math.max(maxMean, 2 * avg));
+    }
+
     /** A sparkline of values (-1 is unknown, blank), relative to the highest. 
*/
     static String spark(long[] values, long max) {
         StringBuilder sb = new StringBuilder();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTabRenderTest.java
index f9f2029e80f8..9eea2ee345eb 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTabRenderTest.java
@@ -17,6 +17,7 @@
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -74,6 +75,46 @@ class MemoryTabRenderTest {
         assertTrue(rendered.contains("Heap"), "Should show Heap memory section 
header");
     }
 
+    @Test
+    void theHeapChartSaysWhatTheTopIsAndWhatIsCommitted() {
+        info.heapMemUsed = 38L * 1024 * 1024;
+        info.heapMemCommitted = 48L * 1024 * 1024;
+        info.heapMemMax = 512L * 1024 * 1024;
+        MetricsCollector metrics = new MetricsCollector();
+        // the heap was 64M a while ago, and has shrunk since
+        LinkedList<Long> history = new LinkedList<>();
+        for (int i = 0; i < 100; i++) {
+            history.add((i < 50 ? 64L : 38L) * 1024 * 1024);
+        }
+        metrics.getHeapMemHistory().put("1234", history);
+
+        String rendered = TuiTestHelper.renderToString(new MemoryTab(ctx, 
metrics), 160, 50);
+
+        assertTrue(rendered.contains("Heap Usage (38M used, 48M committed)"), 
rendered);
+        // the y axis: the top of the chart is the most the heap used, then 
the half
+        assertTrue(rendered.contains("│64M"), rendered);
+        assertTrue(rendered.contains("│32M"), rendered);
+    }
+
+    @Test
+    void theHalfOfOneGigabyteFitsTheAxis() {
+        info.heapMemUsed = 600L * 1024 * 1024;
+        info.heapMemCommitted = 1024L * 1024 * 1024;
+        info.heapMemMax = 2048L * 1024 * 1024;
+        MetricsCollector metrics = new MetricsCollector();
+        LinkedList<Long> history = new LinkedList<>();
+        for (int i = 0; i < 100; i++) {
+            history.add(600L * 1024 * 1024);
+        }
+        metrics.getHeapMemHistory().put("1234", history);
+
+        String rendered = TuiTestHelper.renderToString(new MemoryTab(ctx, 
metrics), 160, 50);
+
+        // 512M is wider than 1G: the axis is as wide as the half, and neither 
label overwrites the border
+        assertTrue(rendered.contains("│  1G"), rendered);
+        assertTrue(rendered.contains("│512M"), rendered);
+    }
+
     @Test
     void renderShowsThreadCount() {
         info.threadCount = 42;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
index d521d7402208..43182a9da38d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessTabRenderTest.java
@@ -24,6 +24,7 @@ import dev.tamboui.text.Span;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -102,4 +103,21 @@ class ProcessTabRenderTest {
         assertTrue(footer.contains("Esc"), "Footer should contain Esc hint");
     }
 
+    @Test
+    void theArgumentsAfterTheJarHaveALineEach() {
+        List<String> parts = ProcessTab.splitCommandLine(
+                "/usr/bin/java -Xmx512m -XX:+AutoCreateSharedArchive -jar 
/opt/camel/lib/camel-launcher.jar run README.md"
+                                                         + " 
application.properties orders.camel.yaml --logging-color=true"
+                                                         + " --name=orders 
--port 8080");
+
+        assertEquals(List.of("/usr/bin/java", "-Xmx512m", 
"-XX:+AutoCreateSharedArchive",
+                "-jar /opt/camel/lib/camel-launcher.jar", "run", "README.md", 
"application.properties",
+                "orders.camel.yaml", "--logging-color=true", "--name=orders", 
"--port 8080"), parts);
+    }
+
+    @Test
+    void theMainClassAfterTheClasspathHasALineOfItsOwn() {
+        assertEquals(List.of("java", "-cp a.jar:b.jar", "com.acme.Main", 
"--verbose"),
+                ProcessTab.splitCommandLine("java -cp a.jar:b.jar 
com.acme.Main --verbose"));
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
index 13be5dae4a48..b7da0484a30b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
@@ -257,6 +257,28 @@ class SourceTabJumpLinksTest {
         
assertThat(tab.computeJumpLinks(billing).get(4).filePath()).isEqualTo(orders.toString());
     }
 
+    @Test
+    void theInfoPanelDescribesTheOpenFileWhenTheViewerHasTheFocus() throws 
Exception {
+        Files.writeString(dir.resolve("application.properties"), 
"greeting=hello\n");
+        Path routes = Files.writeString(dir.resolve("orders.camel.yaml"), """
+                - route:
+                    id: orders
+                    from:
+                      uri: direct:orders
+                      steps:
+                        - to:
+                            uri: log:orders
+                """);
+        SourceTab tab = newTab();
+        assertThat(tab.loadDirectory(dir)).isTrue();
+        // the list has its cursor on its first entry, not on the routes
+        assertThat(tab.infoPath()).isNotNull().isNotEqualTo(routes);
+
+        // the routes are opened another way (as g does): the list keeps its 
cursor, the viewer has the focus
+        tab.openFileForReplay(routes);
+        assertThat(tab.infoPath()).isEqualTo(routes);
+    }
+
     private static SourceTab newTab() {
         return new SourceTab(new MonitorContext(new 
AtomicReference<>(List.of()), new AtomicReference<>(List.of())));
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
index d55de400d4c6..af5c0ed1348b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
@@ -470,4 +470,15 @@ class TimelineTabRenderTest {
         }
         throw new AssertionError("no line with " + text);
     }
+
+    @Test
+    void aMillisecondOfJitterIsNoFullBar() {
+        // a fast route: 0 and 1 ms seconds are noise at the bottom, not spikes
+        assertThat(TimelineTab.meanScale(1, 0)).isEqualTo(10);
+        assertThat(TimelineTab.spark(new long[] { 0, 1, 0, 1 }, 
TimelineTab.meanScale(1, 0)))
+                .doesNotContain("█");
+        // a slow second still stands out against a steady time
+        assertThat(TimelineTab.meanScale(400, 50)).isEqualTo(400);
+        assertThat(TimelineTab.meanScale(60, 50)).isEqualTo(100);
+    }
 }

Reply via email to