This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch chore-tui-endpoint-flow-handoffs
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 516370783a00030426587032f4bb5785992a35e3
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 18:11:41 2026 +0100

    chore: camel-jbang - the flow of Endpoints counts the messages that come in 
and go out, not the hand-offs between the routes
    
    An endpoint one route of the integration sends to and another consumes
    (direct, seda) was counted as an out of the one and an in of the other: the
    message-size example, three timers each feeding a seda queue, showed 50 in
    and 25 out for 25 messages that never left, and an in rate twice that of the
    timers. Since CAMEL-26780 a route called over direct counts its input too, 
so
    every integration with a direct call doubled. The Flow panel and the in/out
    rate chart leave those hand-offs out; the table still lists them.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/EndpointFlow.java  | 59 ++++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/EndpointsTab.java  | 11 ++--
 .../jbang/core/commands/tui/MetricsCollector.java  |  9 ++--
 .../src/main/resources/tui/help/endpoints.md       |  7 +++
 .../jbang/core/commands/tui/EndpointFlowTest.java  | 54 ++++++++++++++++++++
 5 files changed, 126 insertions(+), 14 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java
new file mode 100644
index 000000000000..486d0fefadc0
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * The messages that come into an integration and go out of it, for the flow 
of the Endpoints tab and its rate chart. An
+ * endpoint one route of the integration sends to and another consumes 
(direct, seda) is a hand-off between its routes:
+ * counted as an out of the one and an in of the other, it would count every 
message twice and say it left.
+ */
+final class EndpointFlow {
+
+    private EndpointFlow() {
+    }
+
+    /** The endpoints the integration both sends to and consumes, by their URI 
without options. */
+    static Set<String> handOffs(IntegrationInfo info) {
+        Set<String> in = new HashSet<>();
+        Set<String> out = new HashSet<>();
+        for (EndpointInfo ep : info.endpoints) {
+            if (ep.uri == null) {
+                continue;
+            }
+            if ("in".equals(ep.direction)) {
+                in.add(DiagramTopology.endpointKey(ep.uri));
+            } else if ("out".equals(ep.direction)) {
+                out.add(DiagramTopology.endpointKey(ep.uri));
+            }
+        }
+        in.retainAll(out);
+        return in;
+    }
+
+    /** The hits of the endpoints of the direction the filter takes, without 
the hand-offs between its routes. */
+    static long total(IntegrationInfo info, String direction, 
Predicate<EndpointInfo> filter) {
+        Set<String> handOffs = handOffs(info);
+        return info.endpoints.stream()
+                .filter(ep -> direction.equals(ep.direction) && 
filter.test(ep))
+                .filter(ep -> ep.uri == null || 
!handOffs.contains(DiagramTopology.endpointKey(ep.uri)))
+                .mapToLong(ep -> ep.hits).sum();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index a1d28ce58da3..05dc7467a3cd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -365,14 +365,9 @@ class EndpointsTab extends AbstractTableTab {
             if (chartMode == CHART_SINGLE && selectedUri != null) {
                 renderSingleEndpointChart(frame, chunks.get(1), selectedUri, 
info);
             } else {
-                long inTotal = info.endpoints.stream()
-                        .filter(ep -> "in".equals(ep.direction) && 
matchesFilter(ep))
-                        .mapToLong(ep -> ep.hits)
-                        .sum();
-                long outTotal = info.endpoints.stream()
-                        .filter(ep -> "out".equals(ep.direction) && 
matchesFilter(ep))
-                        .mapToLong(ep -> ep.hits)
-                        .sum();
+                // what comes in and goes out: a hand-off between its routes 
(direct, seda) is neither
+                long inTotal = EndpointFlow.total(info, "in", 
this::matchesFilter);
+                long outTotal = EndpointFlow.total(info, "out", 
this::matchesFilter);
 
                 if (hasSizeHistory) {
                     // three-column layout: flow panel (fixed) | throughput 
chart | payload chart
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java
index 6f555bccd66c..2ffbfe4da81e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java
@@ -241,12 +241,9 @@ class MetricsCollector {
     }
 
     void updateEndpointHistory(IntegrationInfo info) {
-        long inTotal = info.endpoints.stream()
-                .filter(ep -> "in".equals(ep.direction))
-                .mapToLong(ep -> ep.hits).sum();
-        long outTotal = info.endpoints.stream()
-                .filter(ep -> "out".equals(ep.direction))
-                .mapToLong(ep -> ep.hits).sum();
+        // what comes in and goes out: a hand-off between its routes (direct, 
seda) is neither
+        long inTotal = EndpointFlow.total(info, "in", ep -> true);
+        long outTotal = EndpointFlow.total(info, "out", ep -> true);
         long inRemote = info.endpoints.stream()
                 .filter(ep -> "in".equals(ep.direction) && ep.remote)
                 .mapToLong(ep -> ep.hits).sum();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
index 68d24c3f8440..430a77ef4600 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
@@ -49,6 +49,13 @@ The left box lists all inbound components (consumers), the 
right
 box lists all outbound components (producers). The integration
 name sits in the middle, showing the overall message flow direction.
 
+The numbers, and the in and out of the throughput chart, are the
+messages that come into the integration and go out of it. An endpoint
+one of its routes sends to and another consumes (`direct`, `seda`) is
+a hand-off between its routes and is left out: it would count each
+message twice, as an out and an in, and say it left. The table still
+lists it.
+
 ## Throughput Chart
 
 A mirrored sparkline chart shows message rates over time:
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java
new file mode 100644
index 000000000000..354be3b0e870
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The message-size example: three timers each feed a seda queue another route 
of the integration consumes. 25 messages
+ * come in, by the timers, and none goes out; the seda queues are hand-offs 
between its routes.
+ */
+class EndpointFlowTest {
+
+    @Test
+    void aHandOffBetweenItsRoutesIsNoInOrOut() {
+        IntegrationInfo info = new IntegrationInfo();
+        add(info, "small-producer", "in", "timer://small?period=2000", 12);
+        add(info, "small-producer", "out", "seda://small", 12);
+        add(info, "process-small", "in", "seda://small", 12);
+        add(info, "large-producer", "in", "timer://large?period=5000", 5);
+        add(info, "large-producer", "out", "seda://large", 5);
+        add(info, "process-large", "in", "seda://large", 5);
+        // a system it sends to
+        add(info, "process-large", "out", 
"kafka:archive?brokers=localhost:9092", 5);
+
+        
assertThat(EndpointFlow.handOffs(info)).containsExactlyInAnyOrder("seda:small", 
"seda:large");
+        assertThat(EndpointFlow.total(info, "in", ep -> true)).isEqualTo(17);
+        assertThat(EndpointFlow.total(info, "out", ep -> true)).isEqualTo(5);
+    }
+
+    private static void add(IntegrationInfo info, String routeId, String 
direction, String uri, long hits) {
+        EndpointInfo ep = new EndpointInfo();
+        ep.routeId = routeId;
+        ep.direction = direction;
+        ep.uri = uri;
+        ep.hits = hits;
+        info.endpoints.add(ep);
+    }
+}

Reply via email to