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 59b040573e10 CAMEL-23976: TUI - add percentile range bar visualization 
in info panels
59b040573e10 is described below

commit 59b040573e10d3496268e9c4a20d0d66aa74e600
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 10 16:02:34 2026 +0200

    CAMEL-23976: TUI - add percentile range bar visualization in info panels
    
    CAMEL-23976: TUI - streamline Routes/Overview tables and add percentile bar 
to processors
    
    - Remove COVER, AGE, INFLIGHT, SINCE-LAST columns from Routes and Overview 
tables
    - Merge since-last timing into TOTAL/FAIL columns as dimmed suffix
    - Right-align TOTAL/FAIL numbers across rows using max-width padding
    - Add inline percentile bar column after P50/P95/P99 numbers in Routes table
    - Add percentile bar to Processor table for route and processor rows
    - Add MSG/S column to Processor table for consistency with Routes table
    - Widen MSG/S column spacing to separate from TOTAL
    
    CAMEL-23976: TUI - right-align numbers, center headers, add percentile bar 
to Overview
    
    CAMEL-23976: TUI - widen READY column in Overview tab for spacing
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/DiagramTab.java    | 10 ++-
 .../dsl/jbang/core/commands/tui/OverviewTab.java   | 55 +++++++------
 .../dsl/jbang/core/commands/tui/RoutesTab.java     | 91 +++++++++++-----------
 .../dsl/jbang/core/commands/tui/TuiHelper.java     | 38 +++++++++
 4 files changed, 123 insertions(+), 71 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
index 90472df22908..33901d72f665 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
@@ -487,6 +487,8 @@ class DiagramTab extends AbstractTab {
                     lines.add(Line.from(
                             Span.styled(" p99:  ", Theme.muted()),
                             Span.raw(String.format("%" + pw + "d ms", 
route.p99Time))));
+                    lines.add(buildPercentileBarLine(
+                            route.p50Time, route.p95Time, route.p99Time, 
area.width() - 3));
                 }
             }
 
@@ -586,18 +588,17 @@ class DiagramTab extends AbstractTab {
                         Span.raw(ln.id)));
             }
 
-            lines.add(Line.from(Span.raw("")));
             String linkedRoute = diagram.findLinkedRouteId(drillDownRouteId);
             if (linkedRoute != null && diagram.getRouteLayout(linkedRoute) != 
null) {
+                lines.add(Line.from(Span.raw("")));
                 lines.add(Line.from(
                         Span.styled(" ↵ ", Theme.label().bold()),
                         Span.styled(linkedRoute, 
Style.EMPTY.fg(Theme.baseFg()))));
             } else if (ln.treeNode != null && ln.treeNode.info.remote) {
+                lines.add(Line.from(Span.raw("")));
                 String arrow = "from".equals(ln.type) ? " external → " : " → 
external";
                 lines.add(Line.from(
                         Span.styled(arrow, Theme.muted())));
-            } else {
-                lines.add(Line.from(Span.raw("")));
             }
 
             if (ln.treeNode != null && ln.treeNode.info.stat != null) {
@@ -645,6 +646,9 @@ class DiagramTab extends AbstractTab {
                         lines.add(Line.from(
                                 Span.styled(" p99:  ", Theme.muted()),
                                 Span.raw(String.format("%" + pw + "d ms", 
stat.p99ProcessingTime))));
+                        lines.add(buildPercentileBarLine(
+                                stat.p50ProcessingTime, stat.p95ProcessingTime,
+                                stat.p99ProcessingTime, area.width() - 3));
                     }
 
                     if (stat.lastCompletedExchangeTimestamp > 0 || 
stat.lastFailedExchangeTimestamp > 0) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index 899023dfdc25..d887f6d3b06e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -434,6 +434,10 @@ class OverviewTab extends AbstractTab {
             };
         } else {
             boolean hasPercentiles = infos.stream().anyMatch(i -> i.p50Time >= 
0);
+            long maxTotal = infos.stream().mapToLong(i -> 
i.exchangesTotal).max().orElse(0);
+            long maxFailed = infos.stream().mapToLong(i -> 
i.failed).max().orElse(0);
+            int tw = Math.max(numWidth(maxTotal), 6);
+            int fw = Math.max(numWidth(maxFailed), 6);
             for (IntegrationInfo info : infos) {
                 boolean isEven = (rowIndex++ % 2 == 0);
                 Style rowBg = isEven ? Style.EMPTY.bg(Theme.zebra()) : 
Style.EMPTY;
@@ -451,8 +455,6 @@ class OverviewTab extends AbstractTab {
                             Cell.from(Span.styled("", dimStyle)),
                             Cell.from(Span.styled("", dimStyle)),
                             Cell.from(Span.styled(TuiIcons.STOPPED + " 
Stopped", Theme.error().dim())),
-                            Cell.from(Span.styled(info.ago != null ? info.ago 
: "", dimStyle)),
-                            Cell.from(Span.styled("", dimStyle)),
                             Cell.from(Span.styled("", dimStyle)),
                             Cell.from(Span.styled("", dimStyle)),
                             Cell.from(Span.styled("", dimStyle)),
@@ -476,8 +478,6 @@ class OverviewTab extends AbstractTab {
 
                     Style failStyle = info.failed > 0 ? Theme.error().bold() : 
Style.EMPTY;
 
-                    String sinceLastDisplay = formatSinceLast(info);
-
                     boolean hasDoc = info.readmeFiles != null && 
!info.readmeFiles.isEmpty();
                     if (!hasDoc) {
                         hasDoc = hasReadmeInSourceDir(info);
@@ -511,20 +511,27 @@ class OverviewTab extends AbstractTab {
                     } else {
                         timingCol = "";
                     }
+                    Line totalCell = info.sinceLastCompleted != null
+                            ? Line.from(Span.raw(String.format("%" + tw + "d", 
info.exchangesTotal)),
+                                    Span.styled(" (" + info.sinceLastCompleted 
+ ")", Theme.muted()))
+                            : Line.from(Span.raw(String.format("%" + tw + "d", 
info.exchangesTotal)));
+                    Line failCell = info.sinceLastFailed != null
+                            ? Line.from(Span.styled(String.format("%" + fw + 
"d", info.failed), failStyle),
+                                    Span.styled(" (" + info.sinceLastFailed + 
")", Theme.muted()))
+                            : Line.from(Span.styled(String.format("%" + fw + 
"d", info.failed), failStyle));
+
                     rows.add(Row.from(
                             Cell.from(info.pid),
                             Cell.from(nameLine),
                             Cell.from(info.camelVersion != null ? 
info.camelVersion : ""),
-                            centerCell(info.ready != null ? info.ready : "", 
5),
+                            centerCell(info.ready != null ? info.ready : "", 
7),
                             Cell.from(Span.styled(stateText, statusStyle)),
-                            Cell.from(info.ago != null ? info.ago : ""),
                             rightCell(info.routeStarted + "/" + 
info.routeTotal, 7),
                             rightCell(throughputDisplay != null ? 
throughputDisplay : "", 8),
-                            rightCell(String.valueOf(info.exchangesTotal), 8),
-                            rightCell(String.valueOf(info.failed), 6, 
failStyle),
-                            rightCell(String.valueOf(info.inflight), 8),
+                            Cell.from(totalCell),
+                            Cell.from(failCell),
                             rightCell(timingCol, 14),
-                            Cell.from(sinceLastDisplay)).style(rowBg));
+                            Cell.from(buildPercentileBarLine(info.p50Time, 
info.p95Time, info.p99Time, 10))).style(rowBg));
                 }
             }
 
@@ -533,31 +540,27 @@ class OverviewTab extends AbstractTab {
                     Cell.from(Span.styled(sortLabel("PID", "pid"), 
sortStyle("pid"))),
                     Cell.from(Span.styled(sortLabel("NAME", "name"), 
sortStyle("name"))),
                     Cell.from(Span.styled(sortLabel("VERSION", "version"), 
sortStyle("version"))),
-                    centerCell("READY", 5, Style.EMPTY.bold()),
+                    centerCell("READY", 7, Style.EMPTY.bold()),
                     Cell.from(Span.styled(sortLabel("STATUS", "status"), 
sortStyle("status"))),
-                    Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
                     rightCell("ROUTE", 7, Style.EMPTY.bold()),
                     rightCell("MSG/S", 8, Style.EMPTY.bold()),
-                    rightCell(sortLabel("TOTAL", "total"), 8, 
sortStyle("total")),
-                    rightCell(sortLabel("FAIL", "fail"), 6, sortStyle("fail")),
-                    rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
+                    centerCell(sortLabel("TOTAL", "total"), 14, 
sortStyle("total")),
+                    centerCell(sortLabel("FAIL", "fail"), 10, 
sortStyle("fail")),
                     rightCell(timingHeader, 14, Style.EMPTY.bold()),
-                    Cell.from(Span.styled("SINCE-LAST", Style.EMPTY.bold())));
+                    Cell.from(""));
 
             widths = new Constraint[] {
                     Constraint.length(8),
                     Constraint.fill(),
                     Constraint.length(16),
-                    Constraint.length(5),
+                    Constraint.length(7),
                     Constraint.length(10),
-                    Constraint.length(8),
                     Constraint.length(7),
                     Constraint.length(8),
-                    Constraint.length(8),
-                    Constraint.length(6),
-                    Constraint.length(8),
                     Constraint.length(14),
-                    Constraint.length(13)
+                    Constraint.length(10),
+                    Constraint.length(14),
+                    Constraint.fill()
             };
         }
 
@@ -1153,6 +1156,14 @@ class OverviewTab extends AbstractTab {
         return sortStyle(column, topSort);
     }
 
+    private static int numWidth(long... values) {
+        long max = 0;
+        for (long v : values) {
+            max = Math.max(max, Math.abs(v));
+        }
+        return Math.max(String.valueOf(max).length(), 1);
+    }
+
     private int infraSortCompare(InfraInfo a, InfraInfo b) {
         if (a.vanishing != b.vanishing) {
             return a.vanishing ? 1 : -1;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index ce3a7251eaab..975fb02b21e5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -648,6 +648,11 @@ class RoutesTab extends AbstractTab {
         } else {
             boolean hasPercentiles = sortedRoutes.stream().anyMatch(r -> 
r.p50Time >= 0);
 
+            long maxTotal = sortedRoutes.stream().mapToLong(r -> 
r.total).max().orElse(0);
+            long maxFailed = sortedRoutes.stream().mapToLong(r -> 
r.failed).max().orElse(0);
+            int tw = Math.max(numWidth(maxTotal), 6);
+            int fw = Math.max(numWidth(maxFailed), 6);
+
             List<Row> routeRows = new ArrayList<>();
             for (RouteInfo route : sortedRoutes) {
                 Style stateStyle = "Started".equals(route.state)
@@ -658,8 +663,6 @@ class RoutesTab extends AbstractTab {
                         ? Theme.error().bold()
                         : Style.EMPTY;
 
-                String sinceLastRoute = formatSinceLastRoute(route);
-
                 String timingCol;
                 if (hasPercentiles && route.p50Time >= 0) {
                     timingCol = route.p50Time + "/" + route.p95Time + "/" + 
route.p99Time;
@@ -669,18 +672,24 @@ class RoutesTab extends AbstractTab {
                     timingCol = "";
                 }
 
+                Line totalCell = route.sinceLastCompleted != null
+                        ? Line.from(Span.raw(String.format("%" + tw + "d", 
route.total)),
+                                Span.styled(" (" + route.sinceLastCompleted + 
")", Theme.muted()))
+                        : Line.from(Span.raw(String.format("%" + tw + "d", 
route.total)));
+                Line failCell = route.sinceLastFailed != null
+                        ? Line.from(Span.styled(String.format("%" + fw + "d", 
route.failed), failStyle),
+                                Span.styled(" (" + route.sinceLastFailed + 
")", Theme.muted()))
+                        : Line.from(Span.styled(String.format("%" + fw + "d", 
route.failed), failStyle));
+
                 routeRows.add(Row.from(
                         Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
                         Cell.from(routeFromLabel(route)),
                         Cell.from(Span.styled(route.state != null ? 
route.state : "", stateStyle)),
-                        Cell.from(route.uptime != null ? route.uptime : ""),
-                        rightCell(route.coverage != null ? route.coverage : 
"", 6),
                         rightCell(route.throughput != null ? route.throughput 
: "", 8),
-                        rightCell(String.valueOf(route.total), 8),
-                        rightCell(String.valueOf(route.failed), 6, failStyle),
-                        rightCell(String.valueOf(route.inflight), 8),
+                        Cell.from(totalCell),
+                        Cell.from(failCell),
                         rightCell(timingCol, 14),
-                        Cell.from(sinceLastRoute)));
+                        Cell.from(buildPercentileBarLine(route.p50Time, 
route.p95Time, route.p99Time, 10))));
             }
 
             IntegrationInfo selDef = ctx.findSelectedIntegration();
@@ -696,15 +705,12 @@ class RoutesTab extends AbstractTab {
                         Cell.from(Span.styled("TOTAL", ts)),
                         Cell.from(""),
                         Cell.from(""),
-                        Cell.from(""),
-                        Cell.from(""),
                         rightCell(selDef.throughput != null ? 
selDef.throughput : "", 8, ts),
-                        rightCell(String.valueOf(selDef.exchangesTotal), 8, 
ts),
-                        rightCell(String.valueOf(selDef.failed), 6,
-                                selDef.failed > 0 ? Theme.error().bold() : ts),
-                        rightCell(String.valueOf(selDef.inflight), 8, ts),
+                        Cell.from(Span.styled(String.format("%" + tw + "d", 
selDef.exchangesTotal), ts)),
+                        Cell.from(Span.styled(String.format("%" + fw + "d", 
selDef.failed),
+                                selDef.failed > 0 ? Theme.error().bold() : 
ts)),
                         rightCell(totalTimingCol, 14, ts),
-                        Cell.from("")));
+                        Cell.from(buildPercentileBarLine(selDef.p50Time, 
selDef.p95Time, selDef.p99Time, 10))));
             }
 
             String timingHeader = hasPercentiles ? "P50/P95/P99" : 
"MIN/MAX/MEAN";
@@ -715,26 +721,20 @@ class RoutesTab extends AbstractTab {
                             Cell.from(Span.styled(routeSortLabel("ROUTE", 
"name"), routeSortStyle("name"))),
                             Cell.from(Span.styled(routeSortLabel("FROM", 
"from"), routeSortStyle("from"))),
                             Cell.from(Span.styled(routeSortLabel("STATUS", 
"status"), routeSortStyle("status"))),
-                            Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
-                            rightCell("COVER", 6, Style.EMPTY.bold()),
                             rightCell("MSG/S", 8, Style.EMPTY.bold()),
-                            rightCell(routeSortLabel("TOTAL", "total"), 8, 
routeSortStyle("total")),
-                            rightCell(routeSortLabel("FAIL", "failed"), 6, 
routeSortStyle("failed")),
-                            rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
+                            centerCell(routeSortLabel("TOTAL", "total"), 14, 
routeSortStyle("total")),
+                            centerCell(routeSortLabel("FAIL", "failed"), 14, 
routeSortStyle("failed")),
                             rightCell(timingHeader, 14, Style.EMPTY.bold()),
-                            Cell.from(Span.styled("SINCE-LAST", 
Style.EMPTY.bold()))))
+                            Cell.from("")))
                     .widths(
                             Constraint.length(24),
                             Constraint.fill(),
                             Constraint.length(10),
-                            Constraint.length(8),
-                            Constraint.length(6),
-                            Constraint.length(8),
-                            Constraint.length(8),
-                            Constraint.length(6),
-                            Constraint.length(8),
+                            Constraint.length(10),
                             Constraint.length(14),
-                            Constraint.length(13))
+                            Constraint.length(14),
+                            Constraint.length(14),
+                            Constraint.length(12))
                     .highlightStyle(Theme.selectionBg())
                     .highlightSpacing(Table.HighlightSpacing.ALWAYS)
                     
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
@@ -960,6 +960,8 @@ class RoutesTab extends AbstractTab {
                     lines.add(Line.from(
                             Span.styled(" p99:  ", Theme.muted()),
                             Span.raw(String.format("%" + pw + "d ms", 
route.p99Time))));
+                    lines.add(buildPercentileBarLine(
+                            route.p50Time, route.p95Time, route.p99Time, 
area.width() - 3));
                 }
             }
 
@@ -1055,18 +1057,17 @@ class RoutesTab extends AbstractTab {
                         Span.raw(ln.id)));
             }
 
-            lines.add(Line.from(Span.raw("")));
             String linkedRoute = diagram.findLinkedRouteId(drillDownRouteId);
             if (linkedRoute != null && diagram.getRouteLayout(linkedRoute) != 
null) {
+                lines.add(Line.from(Span.raw("")));
                 lines.add(Line.from(
                         Span.styled(" ↵ ", Theme.label().bold()),
                         Span.styled(linkedRoute, 
Style.EMPTY.fg(Theme.baseFg()))));
             } else if (ln.treeNode != null && ln.treeNode.info.remote) {
+                lines.add(Line.from(Span.raw("")));
                 String arrow = "from".equals(ln.type) ? " external → " : " → 
external";
                 lines.add(Line.from(
                         Span.styled(arrow, Theme.muted())));
-            } else {
-                lines.add(Line.from(Span.raw("")));
             }
 
             if (ln.treeNode != null && ln.treeNode.info.stat != null) {
@@ -1114,6 +1115,9 @@ class RoutesTab extends AbstractTab {
                         lines.add(Line.from(
                                 Span.styled(" p99:  ", Style.EMPTY.dim()),
                                 Span.raw(String.format("%" + pw + "d ms", 
stat.p99ProcessingTime))));
+                        lines.add(buildPercentileBarLine(
+                                stat.p50ProcessingTime, stat.p95ProcessingTime,
+                                stat.p99ProcessingTime, area.width() - 3));
                     }
 
                     if (stat.lastCompletedExchangeTimestamp > 0 || 
stat.lastFailedExchangeTimestamp > 0) {
@@ -1272,13 +1276,13 @@ class RoutesTab extends AbstractTab {
             rows.add(Row.from(
                     Cell.from("   route"),
                     Cell.from(Span.styled(route.from != null ? route.from : 
route.routeId, routeStyle)),
-                    Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""),
+                    rightCell(route.throughput != null ? route.throughput : 
"", 8),
                     rightCell(String.valueOf(route.total), 8),
                     rightCell(String.valueOf(route.failed), 6,
                             route.failed > 0 ? Theme.error() : Style.EMPTY),
                     rightCell(String.valueOf(route.inflight), 8),
                     rightCell(routeTimingCol, 14),
-                    Cell.from("")));
+                    Cell.from(buildPercentileBarLine(route.p50Time, 
route.p95Time, route.p99Time, 10))));
 
             for (ProcessorInfo proc : route.processors) {
                 String indent = "  ".repeat(proc.level);
@@ -1296,13 +1300,13 @@ class RoutesTab extends AbstractTab {
                 rows.add(Row.from(
                         Cell.from("   " + (proc.processor != null ? 
proc.processor : "")),
                         Cell.from(Span.styled(indent + (proc.id != null ? 
proc.id : ""), nameStyle)),
-                        Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from(""),
+                        Cell.from(""),
                         rightCell(String.valueOf(proc.total), 8),
                         rightCell(String.valueOf(proc.failed), 6,
                                 proc.failed > 0 ? Theme.error() : Style.EMPTY),
                         rightCell(String.valueOf(proc.inflight), 8),
                         rightCell(procTimingCol, 14),
-                        Cell.from("")));
+                        Cell.from(buildPercentileBarLine(proc.p50Time, 
proc.p95Time, proc.p99Time, 10))));
             }
             String procTimingHeader = hasProcPercentiles ? "P50/P95/P99" : 
"MIN/MAX/MEAN";
 
@@ -1311,7 +1315,7 @@ class RoutesTab extends AbstractTab {
                     .header(Row.from(
                             Cell.from(Span.styled("   TYPE", 
Style.EMPTY.bold())),
                             Cell.from(Span.styled("PROCESSOR", 
Style.EMPTY.bold())),
-                            Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from(""),
+                            rightCell("MSG/S", 8, Style.EMPTY.bold()),
                             rightCell("TOTAL", 8, Style.EMPTY.bold()),
                             rightCell("FAIL", 6, Style.EMPTY.bold()),
                             rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
@@ -1324,11 +1328,8 @@ class RoutesTab extends AbstractTab {
                             Constraint.length(8),
                             Constraint.length(6),
                             Constraint.length(8),
-                            Constraint.length(8),
-                            Constraint.length(6),
-                            Constraint.length(8),
                             Constraint.length(14),
-                            Constraint.length(13))
+                            Constraint.length(12))
                     
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
                             .title(" Processors [" + route.routeId + "] ")
                             .build())
@@ -1707,11 +1708,9 @@ class RoutesTab extends AbstractTab {
                 - **ROUTE** — Unique route identifier (e.g., `timer-to-log`, 
`seda-consumer`)
                 - **FROM** — The endpoint that triggers this route (e.g., 
`timer`, `kafka`, `file`). This is the source of messages
                 - **STATUS** — Route state: `Started` (running), `Stopped` 
(not running), or `Suspended` (paused, can be resumed)
-                - **COVER** — Node coverage: what percentage of route nodes 
have processed at least one message. Shows as `5/10` meaning 5 of 10 nodes were 
reached. Helps find dead code paths — nodes that are defined but never reached. 
100% coverage means all branches in the route have been exercised
                 - **MSG/S** — Current message throughput (messages per second) 
for this route
                 - **TOTAL** — Total exchanges processed by this route since 
startup
                 - **FAIL** — Exchanges that ended with an unhandled error in 
this route
-                - **INFLIGHT** — Exchanges currently being processed by this 
route
                 - **MIN** — Fastest exchange processing time in milliseconds. 
This is the time from when the exchange entered the route until it completed
                 - **MEAN** — Average exchange processing time in milliseconds. 
A rising MEAN may indicate a downstream service getting slower
                 - **MAX** — Slowest exchange processing time in milliseconds. 
A very high MAX compared to MEAN suggests occasional slow outliers
@@ -1740,10 +1739,10 @@ class RoutesTab extends AbstractTab {
                 ## Example Screen
 
                 ```
-                 ROUTE           FROM                  STATUS   COVER  MSG/S  
TOTAL  FAIL  INFLIGHT  P50/P95/P99  SINCE-LAST
-                 timer-to-log    timer://hello?p=2000  Started  5/5    0.50   
142    0     0           1/10/31    1s
-                 timer-to-seda   timer://pump?p=3000   Started  2/2    0.33   
95     0     0           0/1/2      2s
-                 seda-consumer   seda://queue          Started  1/1    0.33   
95     0     0           0/0/1      2s
+                 ROUTE           FROM                  STATUS   MSG/S  TOTAL  
FAIL  P50/P95/P99            SINCE-LAST
+                 timer-to-log    timer://hello?p=2000  Started  0.50   142    
0       1/10/31  ███▒▒▒▒▒░  1s
+                 timer-to-seda   timer://pump?p=3000   Started  0.33   95     
0       0/1/2    ▒░░░░░░░░  2s
+                 seda-consumer   seda://queue          Started  0.33   95     
0       0/0/1               2s
                 ```
 
                 ## Top Mode
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index 240c57e12334..beebc406e1ff 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -599,6 +599,44 @@ final class TuiHelper {
         return "█".repeat(len);
     }
 
+    static Line buildPercentileBarLine(long p50, long p95, long p99, int 
barWidth) {
+        if (p99 <= 0 || barWidth <= 1) {
+            return Line.from(Span.raw(""));
+        }
+        int segments = (p50 > 0 ? 1 : 0) + (p95 > p50 ? 1 : 0) + (p99 > p95 ? 
1 : 0);
+        if (segments < 2) {
+            return Line.from(Span.raw(""));
+        }
+        int greenWidth = (int) Math.round((double) p50 / p99 * barWidth);
+        int yellowWidth = (int) Math.round((double) (p95 - p50) / p99 * 
barWidth);
+        int redWidth = barWidth - greenWidth - yellowWidth;
+        if (greenWidth <= 0 && p50 > 0) {
+            greenWidth = 1;
+        }
+        if (yellowWidth <= 0 && p95 > p50) {
+            yellowWidth = 1;
+        }
+        if (redWidth <= 0 && p99 > p95) {
+            redWidth = 1;
+        }
+        int total = greenWidth + yellowWidth + redWidth;
+        if (total > barWidth) {
+            redWidth = Math.max(0, barWidth - greenWidth - yellowWidth);
+        }
+        List<Span> spans = new ArrayList<>();
+        spans.add(Span.raw(" "));
+        if (greenWidth > 0) {
+            spans.add(Span.styled("█".repeat(greenWidth), Theme.success()));
+        }
+        if (yellowWidth > 0) {
+            spans.add(Span.styled("▒".repeat(yellowWidth), Theme.warning()));
+        }
+        if (redWidth > 0) {
+            spans.add(Span.styled("░".repeat(redWidth), Theme.error()));
+        }
+        return Line.from(spans);
+    }
+
     static String shortTypeName(String type) {
         if (type == null) {
             return "null";

Reply via email to