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;
