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 6264c4653806 chore: camel-jbang - Events shows one category at a time 
with f, and Startup is a waterfall with the slowest first on s (#29507)
6264c4653806 is described below

commit 6264c46538066afe9e8d3a622a05ab17fda35cdf
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 22:52:12 2026 +0100

    chore: camel-jbang - Events shows one category at a time with f, and 
Startup is a waterfall with the slowest first on s (#29507)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/EventTab.java      | 43 +++++++++++++-
 .../dsl/jbang/core/commands/tui/StartupTab.java    | 68 ++++++++++++++++------
 .../src/main/resources/tui/help/event.md           |  3 +
 .../src/main/resources/tui/help/startup.md         | 32 +++++-----
 .../jbang/core/commands/tui/EventTabOrderTest.java | 24 ++++++++
 .../core/commands/tui/StartupTabRenderTest.java    | 24 ++++++++
 6 files changed, 157 insertions(+), 37 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTab.java
index 91fa539b245d..ac2fb44c6bea 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTab.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Rect;
@@ -42,6 +43,9 @@ import static 
org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.*;
 
 class EventTab extends AbstractTableTab {
 
+    // f: only the events of one category (exchange events drown the route and 
context ones); null for all
+    private String category;
+
     EventTab(MonitorContext ctx) {
         super(ctx, "timestamp", "category", "type", "message");
         this.sort = "timestamp";
@@ -73,7 +77,7 @@ class EventTab extends AbstractTableTab {
     @Override
     protected int getRowCount() {
         IntegrationInfo info = ctx.findSelectedIntegration();
-        return info != null ? info.events.size() : 0;
+        return info != null ? sortedEvents(info).size() : 0;
     }
 
     @Override
@@ -109,7 +113,7 @@ class EventTab extends AbstractTableTab {
                         Constraint.length(30),
                         Constraint.fill())
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Events ").build())
+                        .title(category != null ? " Events [" + category + "] 
" : " Events ").build())
                 .build();
 
         lastTableArea = area;
@@ -124,6 +128,9 @@ class EventTab extends AbstractTableTab {
      */
     List<EventInfo> sortedEvents(IntegrationInfo info) {
         List<EventInfo> sorted = new ArrayList<>(info.events);
+        if (category != null) {
+            sorted.removeIf(e -> !category.equals(e.category));
+        }
         if (!sortReversed) {
             Collections.reverse(sorted);
         }
@@ -131,6 +138,38 @@ class EventTab extends AbstractTableTab {
         return sorted;
     }
 
+    @Override
+    protected boolean handleTabKeyEvent(KeyEvent ke) {
+        if (ke.isCharIgnoreCase('f')) {
+            IntegrationInfo info = ctx.findSelectedIntegration();
+            category = nextCategory(category, info != null ? info.events : 
List.of());
+            tableState.select(0);
+            return true;
+        }
+        return false;
+    }
+
+    /** The category after the one shown: all, then each category the events 
have, in order, then all again. */
+    static String nextCategory(String current, List<EventInfo> events) {
+        List<String> categories = events.stream().map(e -> 
e.category).filter(Objects::nonNull).distinct().sorted()
+                .toList();
+        if (current == null) {
+            return categories.isEmpty() ? null : categories.get(0);
+        }
+        int i = categories.indexOf(current);
+        return i >= 0 && i + 1 < categories.size() ? categories.get(i + 1) : 
null;
+    }
+
+    @Override
+    public void renderFooter(List<Span> spans) {
+        super.renderFooter(spans);
+        hint(spans, "f", "category [" + (category != null ? category : "all") 
+ "]");
+    }
+
+    String category() {
+        return category;
+    }
+
     private int sortEvent(EventInfo a, EventInfo b) {
         int result = switch (sort) {
             case "category" -> {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
index 4b54ce450239..a781bc0ad99e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -26,6 +27,7 @@ import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
@@ -55,6 +57,8 @@ class StartupTab extends AbstractTab {
 
     private volatile List<StartupStep> steps = Collections.emptyList();
     private int scrollOffset;
+    // s: the steps in the order they started (a waterfall), or the slowest 
first
+    private boolean slowestFirst;
     private long totalDuration;
     private long maxDuration;
     private long minDurationColor;
@@ -120,6 +124,11 @@ class StartupTab extends AbstractTab {
             loadStartupData();
             return true;
         }
+        if (ke.isCharIgnoreCase('s')) {
+            slowestFirst = !slowestFirst;
+            scrollOffset = 0;
+            return true;
+        }
         return false;
     }
 
@@ -199,7 +208,8 @@ class StartupTab extends AbstractTab {
             return;
         }
 
-        String title = String.format(" Startup Timeline — Total: %dms, Steps: 
%d ", totalDuration, steps.size());
+        String title = String.format(" Startup Timeline — Total: %dms, Steps: 
%d%s ", totalDuration, steps.size(),
+                slowestFirst ? ", slowest first" : "");
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(title)
@@ -217,11 +227,18 @@ class StartupTab extends AbstractTab {
 
         int end = Math.min(scrollOffset + visibleLines, steps.size());
 
-        int barMaxWidth = Math.max(10, inner.width() - 30);
+        List<StartupStep> ordered = ordered(steps, slowestFirst);
+        // the time the steps span: a bar starts where its step started, as 
long as it took
+        long t0 = steps.stream().mapToLong(st -> st.beginTime).filter(b -> b > 
0).min().orElse(0);
+        long t1 = steps.stream().mapToLong(st -> st.beginTime + 
st.duration).max().orElse(0);
+        long span = t0 > 0 ? Math.max(1, t1 - t0) : Math.max(1, maxDuration);
+        int labelWidth = Math.min(inner.width() / 2,
+                steps.stream().mapToInt(st -> 2 * st.level + 
CharWidth.of(label(st))).max().orElse(10) + 2);
+        int barMaxWidth = Math.max(10, inner.width() - 9 - labelWidth - 1);
 
         List<Line> lines = new ArrayList<>();
         for (int i = scrollOffset; i < end; i++) {
-            lines.add(renderStep(steps.get(i), barMaxWidth));
+            lines.add(renderStep(ordered.get(i), labelWidth, barMaxWidth, t0, 
span));
         }
 
         List<Rect> hChunks = Layout.horizontal()
@@ -239,30 +256,42 @@ class StartupTab extends AbstractTab {
         }
     }
 
-    private Line renderStep(StartupStep step, int maxBarWidth) {
-        String indent = "  ".repeat(step.level);
-
+    /**
+     * A step: how long it took, what it is (indented by its level), and its 
bar on the time line of the startup, from
+     * where it started (without begin times, as an older Camel has them, from 
the left).
+     */
+    private Line renderStep(StartupStep step, int labelWidth, int maxBarWidth, 
long t0, long span) {
         boolean isRoot = step.level == 0;
         Style bandStyle = isRoot ? LABEL : colorForDuration(step.duration);
+        int offset = t0 > 0 && step.beginTime > 0
+                ? (int) Math.min(maxBarWidth - 1, Math.round((double) 
(step.beginTime - t0) / span * maxBarWidth)) : 0;
+        int barWidth = Math.max(1, Math.min(maxBarWidth - offset,
+                (int) Math.round((double) step.duration / span * 
maxBarWidth)));
+        String what = TuiHelper.truncate("  ".repeat(step.level) + 
label(step), labelWidth);
+        return Line.from(
+                Span.styled(String.format("%7dms ", step.duration), isRoot ? 
LABEL : VALUE),
+                Span.styled(what + " ".repeat(Math.max(1, labelWidth - 
CharWidth.of(what))), LABEL),
+                Span.raw(" ".repeat(offset)),
+                Span.styled("█".repeat(barWidth), bandStyle));
+    }
 
-        double ratio = maxDuration > 0 ? (double) step.duration / maxDuration 
: 0;
-        int barWidth = Math.max(1, (int) Math.round(ratio * maxBarWidth));
-        String bar = "█".repeat(barWidth);
-
-        String durationStr = step.duration + "ms";
+    private static String label(StartupStep step) {
         String label = step.name != null ? step.name : "";
         if (step.description != null && !step.description.isEmpty() && 
!step.description.equals(label)) {
             label += " " + step.description;
         }
+        return label;
+    }
 
-        int pad = Math.max(1, 8 - durationStr.length());
-
-        return Line.from(
-                Span.raw(indent),
-                Span.styled(bar, bandStyle),
-                Span.raw(" ".repeat(pad)),
-                Span.styled(durationStr, isRoot ? LABEL : VALUE),
-                Span.styled("  " + label, LABEL));
+    /** The steps in the order they started (by begin time, the outer step 
first), or the slowest first. */
+    static List<StartupStep> ordered(List<StartupStep> steps, boolean 
slowestFirst) {
+        List<StartupStep> ordered = new ArrayList<>(steps);
+        if (slowestFirst) {
+            ordered.sort(Comparator.comparingLong((StartupStep st) -> 
st.duration).reversed());
+        } else if (steps.stream().anyMatch(st -> st.beginTime > 0)) {
+            ordered.sort(Comparator.comparingLong((StartupStep st) -> 
st.beginTime).thenComparingInt(st -> st.level));
+        }
+        return ordered;
     }
 
     private Style colorForDuration(long duration) {
@@ -272,6 +301,7 @@ class StartupTab extends AbstractTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
+        hint(spans, "s", slowestFirst ? "start order" : "slowest first");
         hintLast(spans, "PgUp/Dn", "page");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/event.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/event.md
index 4cba30dace71..927669fabb8b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/event.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/event.md
@@ -24,3 +24,6 @@ Only the most recent events are shown.
 - `Up/Down` — select event
 - `s` — cycle sort column
 - `S` — reverse sort order
+- `f` — only the events of one category: all, then each category the
+  events have (`exchange`, `general`, `route` ...), then all again. The
+  exchange events of a busy route drown the route and context ones.
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/startup.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/startup.md
index 0ebda3ba2386..9e06e68bdbe9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/startup.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/startup.md
@@ -1,8 +1,9 @@
 # Startup
 
-The Startup tab shows a timeline of how the integration started up.
-Each step is displayed with a horizontal bar proportional to its
-duration, helping you visually identify which phases took the most time.
+The Startup tab shows a timeline of how the integration started up:
+a waterfall where each step's bar starts when the step started and is
+as long as it took, so you see what ran inside what, and what ran
+after what.
 
 This is useful for optimizing startup time — for example, finding
 components that take a long time to initialize, routes that are slow
@@ -12,25 +13,23 @@ to start, or external connections that delay readiness.
 
 Each line shows one startup step with:
 
-- **Bar** — Horizontal bar proportional to duration. Longer bars = slower 
steps. Color ranges from green (fast) through yellow to red (slowest steps)
 - **Duration** — Time in milliseconds this step took
-- **Name** — What this step does (e.g., component initialization, route 
startup)
+- **Name** — What this step does (e.g., component initialization, route 
startup), indented under the step it is part of
+- **Bar** — Where the step ran on the time line of the startup. Color ranges 
from green (fast) through yellow to red (slowest steps)
 
-Steps are indented to show parent-child relationships — a route startup
-step contains sub-steps for each processor initialization.
+The steps are in the order they started. `s` puts the slowest first,
+to find what to optimize without scrolling through the fast ones.
 
 ## Example Screen
 
 ```
- ████████████████████████████████████████  1234ms  Starting CamelContext
-   ██████████████████                      620ms   Start Routes
-     ████████████                          412ms   timer-to-log
-       ████                                150ms   Create Component: timer
-       ██                                  80ms    Create Component: log
-     ████                                  140ms   seda-consumer
-   ████████                                280ms   Start Consumers
-     ██████                                210ms   timer://hello
-     ██                                    70ms    seda://queue
+  1234ms Starting CamelContext   ████████████████████████████████████████
+   620ms   Start Routes                     ████████████████████
+   412ms     timer-to-log                   █████████████
+   150ms       Create Component: timer      █████
+    80ms       Create Component: log             ███
+   140ms     seda-consumer                               █████
+   280ms   Start Consumers                                    █████████
 ```
 
 In this example, the total startup took 1234ms. The slowest phase
@@ -61,5 +60,6 @@ the context is fully started — the data does not change 
during runtime.
 - `Up/Down` — scroll through steps
 - `PgUp/PgDn` — scroll by page
 - `Home/End` — jump to top/end
+- `s` — the slowest steps first, or the order they started
 - `F5` — reload startup data
 - `Esc` — back
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTabOrderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTabOrderTest.java
index 617ccf94b60b..8591488fd29a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTabOrderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EventTabOrderTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import dev.tamboui.tui.event.KeyEvent;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -46,6 +47,29 @@ class EventTabOrderTest {
                 "ExchangeCompleted@1000", "ExchangeSent@1000", 
"ExchangeSending@1000", "ExchangeCreated@1000");
     }
 
+    @Test
+    void fShowsOneCategoryAtATime() {
+        IntegrationInfo info = new IntegrationInfo();
+        info.events.add(event("ExchangeCreated", 1000));
+        info.events.add(event("ExchangeCompleted", 1000));
+        EventInfo started = event("RouteStarted", 500);
+        started.category = "route";
+        info.events.add(started);
+        EventTab tab = new EventTab(new MonitorContext(new 
AtomicReference<>(List.of(info)), new AtomicReference<>(List.of())));
+        tab.ctx.selectedPid = info.pid = "1";
+
+        // all, then exchange, route, and all again
+        assertThat(tab.category()).isNull();
+        tab.handleKeyEvent(KeyEvent.ofChar('f'));
+        assertThat(tab.category()).isEqualTo("exchange");
+        tab.handleKeyEvent(KeyEvent.ofChar('f'));
+        assertThat(tab.category()).isEqualTo("route");
+        assertThat(tab.sortedEvents(info)).extracting(e -> 
e.type).containsExactly("RouteStarted");
+        tab.handleKeyEvent(KeyEvent.ofChar('f'));
+        assertThat(tab.category()).isNull();
+        assertThat(tab.sortedEvents(info)).hasSize(3);
+    }
+
     private static EventInfo event(String type, long timestamp) {
         EventInfo e = new EventInfo();
         e.type = type;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
index e4dd9a4118f1..0c709b0b983e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.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;
 
 /**
@@ -81,4 +82,27 @@ class StartupTabRenderTest {
                 "Footer should contain hints");
     }
 
+    @Test
+    void theStepsAreInTheOrderTheyStartedOrTheSlowestFirst() {
+        // as the recorder has them: a step after its parent, in the order 
they ended
+        StartupTab.StartupStep routes = step("Init Routes", 1, 100, 80);
+        StartupTab.StartupStep route = step("stock-check Create Route", 2, 
105, 73);
+        StartupTab.StartupStep context = step("Init CamelContext", 0, 90, 256);
+        List<StartupTab.StartupStep> steps = List.of(route, routes, context);
+
+        assertEquals(List.of(context, routes, route), 
StartupTab.ordered(steps, false));
+        assertEquals(List.of(context, routes, route), 
StartupTab.ordered(steps, true));
+        // without begin times (an older Camel): as recorded
+        steps.forEach(st -> st.beginTime = 0);
+        assertEquals(steps, StartupTab.ordered(steps, false));
+    }
+
+    private static StartupTab.StartupStep step(String name, int level, long 
begin, long duration) {
+        StartupTab.StartupStep st = new StartupTab.StartupStep();
+        st.name = name;
+        st.level = level;
+        st.beginTime = begin;
+        st.duration = duration;
+        return st;
+    }
 }

Reply via email to