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

davsclaus pushed a commit to branch CAMEL-26691-timeline-tui
in repository https://gitbox.apache.org/repos/asf/camel.git

commit be61e3e89d190b932372c7af573357bf8dc4346b
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 6 23:36:06 2026 +0100

    CAMEL-26691: timeline - the redelivery attempts of the processors, as the 
route misses a recovery when the redelivered header is removed
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/Timeline.java      | 22 +++++++++++++++++-----
 .../jbang/core/commands/tui/TimelineAiPrompt.java  |  4 ++++
 .../dsl/jbang/core/commands/tui/TimelineTab.java   | 20 +++++++++++++++++---
 .../dsl/jbang/core/commands/tui/TimelineTest.java  | 20 ++++++++++++++++++++
 4 files changed, 58 insertions(+), 8 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
index a0fb3ce06fca..7ae4d24a3506 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Timeline.java
@@ -49,14 +49,19 @@ final class Timeline {
     private static final int REDELIVERIES = 3;
     private static final int INFLIGHT = 4;
     private static final int MEAN = 5;
+    private static final int ATTEMPTS = 6;
 
     private static final Pattern STATUS_CODE = Pattern.compile("statusCode: 
?(\\d{3})");
 
     private final Map<String, Recording> byPid = new ConcurrentHashMap<>();
 
-    /** The exchanges of a route per second, oldest first; a second before the 
first sample is -1. */
+    /**
+     * The exchanges of a route per second, oldest first; a second before the 
first sample is -1. The attempts are the
+     * redeliveries of its processors (each attempt), which also show a 
recovery the route did not count: the route
+     * counts an exchange done after a redelivery by the redelivered header, 
which a removeHeaders("Camel*") takes away.
+     */
     record RouteSeries(String route, long[] completed, long[] recovered, 
long[] handled, long[] failed, long[] inflight,
-            long[] mean, long[] down) {
+            long[] mean, long[] down, long[] attempts) {
 
         long total(long[] values) {
             long sum = 0;
@@ -111,8 +116,12 @@ final class Timeline {
             Map<String, long[]> counters = new LinkedHashMap<>();
             for (RouteInfo r : info.routes) {
                 if (r.routeId != null) {
-                    counters.put(r.routeId,
-                            new long[] { r.total, r.failed, r.failuresHandled, 
r.redeliveries, r.inflight, r.meanTime });
+                    long attempts = 0;
+                    for (ProcessorInfo p : r.processors) {
+                        attempts += p.redeliveries;
+                    }
+                    counters.put(r.routeId, new long[] {
+                            r.total, r.failed, r.failuresHandled, 
r.redeliveries, r.inflight, r.meanTime, attempts });
                 }
             }
             rec.routes.put(second, counters);
@@ -308,6 +317,7 @@ final class Timeline {
         long[] failed = filled(seconds);
         long[] inflight = filled(seconds);
         long[] mean = filled(seconds);
+        long[] attempts = filled(seconds);
         long[] down = new long[seconds];
         long prevSecond = -1;
         long[] prev = null;
@@ -326,6 +336,7 @@ final class Timeline {
                 long dHandled = Math.max(0, cur[HANDLED] - prev[HANDLED]);
                 long dRecovered = Math.max(0, cur[REDELIVERIES] - 
prev[REDELIVERIES]);
                 long dOk = Math.max(0, dTotal - dFailed - dHandled - 
dRecovered);
+                long dAttempts = Math.max(0, cur[ATTEMPTS] - prev[ATTEMPTS]);
                 // the mean of the exchanges done since the last sample, from 
the means of all of them
                 long dTime = cur[MEAN] * cur[TOTAL] - prev[MEAN] * prev[TOTAL];
                 long m = dTotal > 0 ? Math.max(0, dTime / dTotal) : -1;
@@ -340,6 +351,7 @@ final class Timeline {
                     recovered[i] = share(dRecovered, span, k);
                     handled[i] = share(dHandled, span, k);
                     failed[i] = share(dFailed, span, k);
+                    attempts[i] = share(dAttempts, span, k);
                     mean[i] = m;
                     if (t < s && inflight[i] < 0) {
                         inflight[i] = cur[INFLIGHT];
@@ -354,7 +366,7 @@ final class Timeline {
                 down[(int) (en.getKey() - start)] = 1;
             }
         }
-        return new RouteSeries(route, completed, recovered, handled, failed, 
inflight, mean, down);
+        return new RouteSeries(route, completed, recovered, handled, failed, 
inflight, mean, down, attempts);
     }
 
     /** The k-th of n parts of a count: the parts add up to the count, the 
later ones a little bigger. */
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineAiPrompt.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineAiPrompt.java
index 207f69130e43..d3e055e4b44f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineAiPrompt.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineAiPrompt.java
@@ -67,6 +67,10 @@ final class TimelineAiPrompt {
                 }
                 sb.append(" (handled or failed 
").append(when(trouble)).append(')');
             }
+            long attempts = r.total(r.attempts());
+            if (attempts > 0) {
+                sb.append(", ").append(attempts).append(" redelivery attempts 
of its processors ").append(when(r.attempts()));
+            }
             long down = r.total(r.down());
             if (down > 0) {
                 sb.append(", down ").append(down).append("s by a chaos rule");
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 ec78fcb0fe1e..394e218a21f3 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
@@ -215,7 +215,7 @@ class TimelineTab extends AbstractTableTab {
         boolean anyCause = causes.stream().anyMatch(c -> sum(c.values()) > 0);
         int causeHeight = anyCause ? CAUSE_ROWS + 1 : 1;
         // the cause, the legend of the routes, a line per route and the time 
axis, in a border
-        int detailHeight = DETAIL_ROWS + 5;
+        int detailHeight = DETAIL_ROWS + 6;
         int topHeight = Math.max(6, Math.min(area.height() - detailHeight, 
causeHeight + routes.size() + 4));
         List<Rect> rows = Layout.vertical()
                 .constraints(Constraint.length(topHeight), 
Constraint.length(detailHeight), Constraint.fill())
@@ -322,6 +322,7 @@ class TimelineTab extends AbstractTableTab {
         long[] handled = cols(r.handled(), false);
         long[] failed = cols(r.failed(), false);
         long[] down = cols(r.down(), true);
+        long[] attempts = cols(r.attempts(), false);
         long max = 1;
         for (int c = 0; c < columns; c++) {
             max = Math.max(max, total(completed[c], recovered[c], handled[c], 
failed[c]));
@@ -338,7 +339,7 @@ class TimelineTab extends AbstractTableTab {
                 int level = (int) Math.max(1, Math.min(8, Math.ceil(t * 8.0 / 
max)));
                 Style style = failed[c] > 0 ? Theme.error()
                         : handled[c] > 0 ? handledStyle()
-                        : recovered[c] > 0 ? Theme.warning() : Theme.success();
+                        : recovered[c] > 0 || attempts[c] > 0 ? 
Theme.warning() : Theme.success();
                 spans.add(Span.styled(String.valueOf(BLOCKS.charAt(level - 
1)), style));
             }
         }
@@ -371,6 +372,8 @@ class TimelineTab extends AbstractTableTab {
             spans.add(Span.styled("  " + r.total(r.handled()) + " handled", 
handledStyle()));
         } else if (r.total(r.recovered()) > 0) {
             spans.add(Span.styled("  " + r.total(r.recovered()) + " 
recovered", Theme.warning()));
+        } else if (r.total(r.attempts()) > 0) {
+            spans.add(Span.styled("  " + r.total(r.attempts()) + " 
redeliveries", Theme.warning()));
         } else if (done == 0) {
             spans.add(Span.styled("  idle", Theme.muted()));
         }
@@ -431,7 +434,7 @@ class TimelineTab extends AbstractTableTab {
         List<long[]> values = List.of(cols(r.completed(), false), 
cols(r.recovered(), false),
                 cols(r.handled(), false), cols(r.failed(), false));
         List<Style> styles = List.of(Theme.success(), Theme.warning(), 
handledStyle(), Theme.error());
-        int rows = Math.max(2, Math.min(DETAIL_ROWS, area.height() - 5));
+        int rows = Math.max(2, Math.min(DETAIL_ROWS, area.height() - 6));
         lines.addAll(stacked(values, styles, rows, columns, label));
 
         long[] mean = cols(r.mean(), true);
@@ -469,6 +472,15 @@ class TimelineTab extends AbstractTableTab {
                 Span.styled(spark(inflight, maxInflight), Theme.notice()),
                 Span.raw("  now " + last + (maxInflight > last ? ", max " + 
maxInflight : ""))));
 
+        long[] attempts = cols(r.attempts(), false);
+        long maxAttempts = 0;
+        for (long v : attempts) {
+            maxAttempts = Math.max(maxAttempts, v);
+        }
+        lines.add(Line.from(Span.styled(pad("   redelivery", label), 
Theme.label()),
+                Span.styled(spark(attempts, maxAttempts), Theme.warning()),
+                Span.raw("  " + r.total(r.attempts()) + " attempts")));
+
         List<Span> totals = new ArrayList<>();
         totals.add(Span.raw(pad("", label)));
         addTotal(totals, r.total(r.completed()), "completed", Theme.success());
@@ -683,6 +695,7 @@ class TimelineTab extends AbstractTableTab {
             row.put("handled", r.total(r.handled()));
             row.put("failed", r.total(r.failed()));
             row.put("downSeconds", r.total(r.down()));
+            row.put("redeliveryAttempts", r.total(r.attempts()));
             JsonObject perSecond = new JsonObject();
             perSecond.put("completed", array(r.completed()));
             perSecond.put("recovered", array(r.recovered()));
@@ -690,6 +703,7 @@ class TimelineTab extends AbstractTableTab {
             perSecond.put("failed", array(r.failed()));
             perSecond.put("inflight", array(r.inflight()));
             perSecond.put("meanMillis", array(r.mean()));
+            perSecond.put("redeliveryAttempts", array(r.attempts()));
             row.put("perSecond", perSecond);
             rows.add(row);
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
index ca94a641994f..ea2089b8e239 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTest.java
@@ -166,6 +166,26 @@ class TimelineTest {
         assertThat(orders(S0 + 3, 4).down()).containsExactly(1, 1, 1, 1);
     }
 
+    @Test
+    void 
theRedeliveryAttemptsOfTheProcessorsShowARecoveryTheRouteDidNotCount() {
+        IntegrationInfo info = info(10, 0, 0, 0, 0);
+        ProcessorInfo call = new ProcessorInfo();
+        call.redeliveries = 4;
+        info.routes.get(0).processors.add(call);
+        timeline.record(info, T0);
+        // a removeHeaders("Camel*") after the call took the redelivered 
header away: the route counted no recovery
+        IntegrationInfo later = info(12, 0, 0, 0, 0);
+        ProcessorInfo again = new ProcessorInfo();
+        again.redeliveries = 7;
+        later.routes.get(0).processors.add(again);
+        timeline.record(later, T0 + 1000);
+
+        Timeline.RouteSeries r = orders(S0 + 1, 1);
+        assertThat(r.recovered()).containsExactly(0);
+        assertThat(r.attempts()).containsExactly(3);
+        assertThat(r.completed()).containsExactly(2);
+    }
+
     private static ErrorInfo error(String route, String node, String message, 
long repeat, long timestamp) {
         ErrorInfo e = new ErrorInfo();
         e.routeId = route;

Reply via email to